System and method for generating and transferring data

ABSTRACT

A computer site includes a data generating source. In one implementation, the source includes a device that can be manipulated by a patient to sense some function or condition of the patient. One example is a heart monitor that a patient can hold against his or her chest. The device outputs an audible signal responsive to the function or condition, such as the beating of the heart. A computer that can be operated by the patient or other initial user at the site runs a program that processes an electric signal generated in response to the audible signal as received through a microphone connected to the computer. The program is downloaded or otherwise accessed through a computer communication network, such as the Internet. The computer sends resulting data signals over the computer communication network. Using a plurality of the foregoing components, any number of patients (or other users) that can access the computer communication network can provide real-time information about their personal medical condition (or other generated data) to their personal medical care providers (or other remote end users).

BACKGROUND OF THE INVENTION

[0001] This invention relates generally to systems and methods for generating and transferring medical and other data. A specific implementation includes a remote heart monitoring method.

[0002] Many industries use real-time information, but the medical field is one in which such information is particularly critical. In hospitals, for example, patients may have their biological functions or conditions continuously monitored so that any significant change can be immediately detected and addressed by appropriate personnel. Such monitoring includes generating the information and transferring it to where it is to be used.

[0003] U.S. Pat. No. 5,481,255 to Albert et al. discloses transmitting medical data, for example, through a paging network to a pager receiver connected to a computer, such as a palmtop computer held by a physician. In one example, a patient is connected to an electrocardiogram (ECG) machine to generate ECG information that is processed and transmitted through the paging network to the receiver and attached computer.

[0004] U.S. Pat. No. 5,735,285 to Albert et al. discloses another communication system for conveying ECG data or other biomedical waveform data between a patient and an attending physician's location. The patient employs a HEART CARD™ device from Instromedix, Inc., or the like, which converts the patient's ECG signal into a frequency modulated audio signal that may then be analyzed by audio inputting via a telephone system to a selected hand-held computer device with integrated microphone and audio system. The computer device functions to digitize, record and demodulate the frequency modulated signal for presentation and viewing on the hand-held computer. The audio signal can also be input directly into a personal computer (PC) via a standard personal computer microphone. The audio signal can be input directly into a PC through a phone line connection via a standard “voice-capable” modem, for example. The stored ECG audio signal can be retransmitted via telephone, either wireline or wireless. At a receiving end, a programmed hand-held computer can be used to receive the audio FM biomedical signal for digitization, recording and demodulation for viewing. Such computer can be one with integrated microphone, audio analog to digital converter, digital to analog converter, speaker, and central processing unit with memory for performing various computational, data storage and signal processing tasks.

[0005] Despite previously disclosed or implemented systems, there is still the need for a novel and improved system and method for generating and transferring medical (or other) data, especially a system and method which enable a patient to communicate with a medical care provider in real time virtually from anywhere. Preferably such a system and method should be able to communicate globally, such as via the Internet. Such a system and method should be inexpensive, simple, and easy-to-use. This includes use of a computer without having to purchase or manually install a specialized computer application program or any special hardware Such system and method should be relatively inexpensive both for the patient or other input user and the medical care provider or other recipient to use (e.g., minimal equipment requirements, no long distance telephone tolls). Such system and method should quickly (ideally, in real time) provide accurate data from the input user to the recipient.

SUMMARY OF THE INVENTION

[0006] The present invention meets the aforementioned needs by providing a novel and improved system and method for generating and transferring medical and other data. For example, the system and method permit the monitoring of biological functions and conditions of a plurality of patients and the reporting of the resulting data to respective medical care providers. In a particular implementation, the present invention provides for remote heart monitoring.

[0007] In a preferred embodiment, a device that can be manipulated by a patient senses some function or condition of the patient. One example is a small, inexpensive, hand-held heart monitor that a patient can hold against his or her chest; when used, the device outputs an audible signal responsive to the beating of the heart, such as the electrocardiogram. A computer that can be operated by the patient runs a program that processes an electric signal generated in response to the audible signal as received through a microphone connected to the computer. The computer can send resulting data signals over a computer communication network and preferably over a global communication network such as the Internet. Using a plurality of the foregoing components, any number of patients that can access the computer communication network can quickly, easily, inexpensively, and accurately provide real-time information about their personal medical condition to their personal medical care providers. The real-time information can also be used locally such as by the individual during exercise or simply while trying to monitor or modulate the sensed condition (e.g., to lower heart rate, blood pressure, or stress).

[0008] One definition of the system for generating and transferring medical data of the present invention comprises: means for sensing a biological function or condition; and means, communicating with the means for sensing, for transferring a response to the sensed biological function or condition over the Internet.

[0009] In accordance with another definition of the present invention, the system comprises a sensor used by a human user to sense a function or condition of the user's body. It also comprises a personal computer located with the sensor. The personal computer has a microphone connector and analog to digital conversion means which are communicated with the sensor such that a digital processing circuit of the personal computer receives from the analog to digital conversion means a digital electric signal derived from an analog electric signal received through the microphone connector in response to the user's body function or condition sensed by the sensor. The personal computer is programmed to transfer data representative of the received digital electric signal over a computer communication network contemporaneously with the sensor sensing the user's body function or condition. This system can further comprise a recipient computer located at a medical facility and connected to communicate with the computer communication network, wherein the recipient computer is programmed to receive the data transferred over the computer communication network and to provide a visible representation of the sensed body function or condition and to store the data in a searchable database. The system can comprise a recipient computer located at a data communication service provider facility and connected to communicate with the computer communication network, wherein the recipient computer is programmed to receive the data transferred over the computer communication network and to transmit in response thereto another signal to an end user.

[0010] Still another definition of the present invention for a system for generating and transferring medical data comprises: a sensor to provide an acoustical signal in simultaneous response to a biological function or condition; a microphone, located with the sensor, to receive the acoustical signal as the acoustical signal is provided from the sensor; and a computer connected to the microphone and adapted to transfer over the Internet, in real-time response to the microphone receiving the acoustical signal, electric signals representative of the received acoustical signal. In one embodiment the computer includes a personal computer programmed with a web browser and a medical data acquisition and transmission program. The medical data acquisition and transmission program can be downloaded from an Internet site accessed using the web browser.

[0011] The system of the present invention can also be defined without limitation as to type of data, such as to comprise: a plurality of initial user sites, each of the initial user sites having a data generating source and a computer connected to receive signals from the data generating source and to access the Internet; and an Internet site addressable from each of the plurality of initial user sites such that each initial user site can access a data acquisition and transmission program through the Internet site to enable the respective computer to process received signals from the respective data generating source and transmit responsive signals over the Internet. Each initial user site preferably can transmit simultaneously over the Internet to one or more recipient sites. Also, the receiving computer at a recipient site can receive and display in real time the signals from one or a multiple number of initial user sites.

[0012] A method of generating and transferring medical data in accordance with an aspect of the present invention comprises: transducing directly from a patient a human body function or condition into a first signal; converting at the site of the patient the first signal into a second signal adapted for transmission over the Internet; and transmitting the second signal over the Internet to a recipient. In one implementation the method further comprises performing the transducing, converting, and transmitting contemporaneously; and contemporaneously therewith generating at the recipient a display in response to the transmitted second signal, wherein the display is a real-time representation of the transduced human body function or condition.

[0013] A method of generating and transferring data in accordance with the present invention comprises: accessing an Internet site from a computer where a data generating source is located; downloading to the computer from the accessed Internet site a data acquisition and transmission program; and operating the computer with the downloaded data acquisition and transmission program such that the computer receives signals from the data generating source, processes the received signals, and transmits data signals onto the Internet in response thereto. In particular implementations, the downloaded program may or may not operate unless the computer is connected to the specific web site on the Internet from which the program was obtained.

[0014] A method of the present invention for generating medical data representative of a biological function or condition of an individual comprises:

[0015] (a) accessing an Internet site with a personal computer located with the individual, wherein the personal computer is programmed with a conventional operating program;

[0016] (b) downloading from the accessed Internet site to the personal computer a medical data acquisition program automatically operable with the conventional operating program;

[0017] (c) generating an acoustical signal in response to the biological function or condition of the individual;

[0018] (d) receiving the acoustical signal in a microphone near the individual and converting with the microphone the received acoustical signal into an analog electric signal;

[0019] (e) communicating the analog electric signal from the microphone to an analog to digital conversion circuit of the personal computer and converting thereby the analog electric signal to a digital electric signal;

[0020] (f) processing the digital electric signal in the personal computer under control of the medical data acquisition program; and

[0021] (g) displaying to the individual, through the personal computer, a representation of the individual's biological function or condition in response to which the acoustical signal was generated.

[0022] As a step (h), responsive data can be transmitted over the Internet to other sites for storage and review. From such other site or sites, for example, data can then be transmitted over a paging or other wireless network to a physician, for example. In a preferred embodiment, at least steps (c) through (h) are performed together in real time.

[0023] Still another definition of a method of monitoring biological functions and conditions of a plurality of patients in accordance with the present invention comprises: distributing to each patient at least one sensor to detect at least one biological function or condition of the patient; and maintaining a medical data acquisition and transmission program at an Internet site accessible by the patients such that the patients can use, from computers at the locations of the patients, the medical data acquisition and transmission program to control their respective computers to receive and process signals from the patients' respective sensors and to transmit medical data onto the Internet in response thereto. This method can further comprise distributing to a plurality of physicians receivers (with or without transmitting capability) for receiving at least portions of the medical data transmitted over the Internet. It can still further comprise providing a combination Internet receiving and paging or other wireless communication network transmitting site to receive the medical data transmitted on the Internet and to transmit received medical data to at least one respective physician's receiver through the wireless network. The method can also comprise marking each sensor with indicia defining the address of the Internet site.

[0024] Therefore, from the foregoing, it is a general object of the present invention to provide a novel and improved system and method for generating and transferring medical and other data. Other and further objects, features and advantages of the present invention will be readily apparent to those skilled in the art when the following description of the preferred embodiments is read in conjunction with the accompanying drawings.

BRIEF DESCRIPTION OF THE DRAWINGS

[0025]FIG. 1 is a block diagram illustrating one configuration of a system of the present invention.

[0026]FIG. 2 is a block diagram illustrating one variation of the FIG. 1 configuration wherein a patient site accesses a log-on site to acquire a software program necessary to provide medical data to a recipient site.

[0027]FIG. 3 is a block diagram illustrating one implementation of the system depicted in FIG. 2.

[0028]FIG. 4 is a block diagram illustrating another implementation of the system depicted in FIG. 2.

[0029]FIG. 5 illustrates one implementation for a patient site.

[0030]FIG. 6 is a general flow diagram for the use and operation of the implementations of FIGS. 2-5.

[0031]FIG. 7 is a more detailed block diagram of the patient site depicted in FIG. 5.

[0032]FIG. 8 illustrates a particular implementation of a system of the present invention including at least one patient site of the type shown in FIG. 7.

[0033]FIG. 9 is a flow diagram for a medical data acquisition and transmission program for the FIG. 8 system.

[0034]FIG. 10 is a flow diagram for displaying data at a recipient Internet site remote from the patient's site of the FIG. 8 system.

DETAILED DESCRIPTION OF THE INVENTION

[0035] A general representation of the system of the present invention as applied to one example of medical data acquisition and transmission is illustrated in FIG. 1. The present invention also can be used for generating and transferring other data that can be processed in the manner described below.

[0036] Although only one patient site need be present, typically there will be a plurality of patient sites 2 a-2 n (the term “patient” as used herein is not limited to someone who is ill or who is necessarily under a medical provider's care; it simply refers to an individual using the present invention in the medical context). These sites are wherever the patient (or, more broadly, initial user or simply some data generating source) is and has access to a computer and communication network, which is specifically illustrated as including the Internet 4 in the drawings and in the remainder of the following description of the present invention (the term “Internet” as used herein encompasses the global computer network commonly known by that name, any functional part of it suitable for use in the present invention (e.g., the World Wide Web), and any other suitable global computer network capable of providing the communication link of the present invention). Also connected to the Internet 4 is at least one remote site 6 with which the patient sites 2 a-2 n can communicate.

[0037] The remote Internet site(s) 6 can be embodied in various manners; however, two particular types of the preferred embodiment are represented in FIG. 2. One or more log-on sites 6 a are addressed by a patient at his or her site entering the log-on site's address. Such a log-on site 6 a is the only site a patient need explicitly address from the respective patient site 2.

[0038] Although the foregoing is the only explicit address a patient need enter, other remote sites can be accessed, such as illustrated by recipient sites 6 b in FIG. 2. In preferred embodiments, such sites are automatically addressed when data is to be sent from a patient site 2 to an end user. This can occur through a log-on site 6 a, but in the illustrated embodiment of FIG. 2 there are physically and functionally distinct recipient Internet sites.

[0039] A further overview of the foregoing will be described with reference to FIGS. 3-6. A more detailed explanation will then be given with reference to FIGS. 7-10.

[0040] Referring to FIG. 3, a patient site 2 includes means for sensing a biological function or condition (more broadly, a data generating source; for example, a device that responds to some ambient parameter by generating a signal adaptable for use by a computer as described herein). It also includes means, communicating with the means for sensing, for transferring a response to the sensed biological function or condition over the Internet. The means for sensing includes at least one sensor 8 suitable for sensing a function or condition of the body of the respective patient. The means for transferring includes a computer 10 connected to receive signals from the one or more sensors 8 and to access the Internet 4.

[0041] In a particular implementation, the sensor 8 includes a hand-held device that is inexpensive and simple to use. As illustrated in FIG. 5, the sensor 8 includes a heart monitor that provides an audible output signal in response to the sensed biological function or condition, which in this example is the patient's heart activity. The patient can hold this device against his or her chest by hand, this can be done by another person, or the device can be attached to the chest or held by an elastic strap (for example). The sensor 8 of this implementation in FIG. 5 includes a hand-held transducer that converts energy from an adjacent beating heart of a patient 12 into an acoustical signal. The acoustical signal can be detected by a microphone 14 connected to the conventional microphone jack of a personal computer 16 implementing the computer 10. The term “personal computer” as used in this specification and claims encompasses all types of computers conventionally referred to by that name regardless of size designation (e.g., desktop, laptop, notebook, hand-held, palmtop, etc.). Non-limiting examples of sensors are those capable of providing outputs responsive to (1) changes of electrical potential occurring during the heartbeat to provide ECG data, or (2) brain waves to provide EEG data, or (3) variations in the size of an organ or limb and in the amount of blood present or passing through it to provide plethysmograph data, or (4) galvanic skin resistance.

[0042] It is to be noted that any particular sensor 8 merely needs to provide some signal suitable for inputting to the computer 10 (thus “data” of the present invention includes anything represented by such signal). The microphone 14 is used in the embodiment of FIG. 5 to convert the acoustical signal of this embodiment into a corresponding analog electric signal used by the personal computer 16. The sensor 8 can be a type that provides an electric signal output directly to the personal computer 16, for example.

[0043] The personal computer 16 in the FIG. 5 implementation is connected to the microphone 14 and includes circuits and programs to access the Internet 4 and to transfer a representation of the sensor's output signal over the Internet 4 to a selected Internet site in response to input to the personal computer 16 from the microphone 14. That is, the personal computer 16 transfers over the Internet 4 data representative of the electric signal received either from the microphone or other device, such as the sensor 8 itself, into the personal computer 16. The personal computer 16 need be programmed initially only with conventional programs sufficient to enable the patient or other user at the patient site to access the Internet 4 through the personal computer 16. This programming can include a conventional web browser. At some point in the use of the personal computer 16, it is also programmed with a medical data (or other specific type of data as used for other embodiments) acquisition and transmission program (in broader terms, simply a data acquisition and transmission program). In the preferred embodiment, this program is downloaded into the personal computer 16 from the log-on site 6 a using the web browser. The program can be in any suitable form, such as in the form of an application “plug-in,” a “Java applet,” or in a preferred particular implementation an “ActiveX control.” The personal computer 16 connects to the Internet 4 by any suitable communication link (e.g., telephone line, television cable).

[0044] The circuitry and programming of the personal computer 16 also ultimately are sufficient to provide it with the capability to read the input port receiving the signal from the microphone 14 or other input device, to process the input signal, to access the recipient Internet site to which data is to be transferred, and to send the data to the recipient site over the Internet.

[0045] Referring to FIG. 3 again, the log-on site 6 a in this embodiment includes a computer 18 having the medical data acquisition and transmission program stored for downloading to a patient site 2. That is, the log-on site 6 of this implementation is an Internet site addressable from each of the plurality of patient sites such that each patient site can access the medical data acquisition and transmission program through the Internet site to enable the respective computer 10 (e.g., personal computer 16) at the respective patient site to process received signals from a patient sensor connected to it and to transmit responsive signals over the Internet 4.

[0046] The log-on site 6 can be implemented in any suitable manner. For example, it can be provided by an existing Internet service provider which the computer at a patient site accesses when the patient initially logs onto the Internet.

[0047] Still referring to FIG. 3, the recipient site 6 b includes a computer 20 programmed to receive data and display it through a display 22 (e.g., a monitor or printer). In the implementation of FIG. 3, the computer 20 receives the transferred response over the Internet and directly communicates the information via the display 22. For example, the computer 20 can be located at a medical facility and connected to communicate with the Internet. Such computer is programmed to receive the response transferred over the Internet and to provide a visible representation of the sensed biological function or condition.

[0048] Another implementation of a recipient site 6 b is illustrated in FIG. 4. In this implementation, the recipient site 6 b functions as an intermediary between a patient site and an end user of the data. As shown in FIG. 4, the recipient site 6 b still includes the computer 20, but which is additionally programmed to transfer the data to something other than the display 22. In FIG. 4, the computer 20 is programmed to communicate the data to a wireless network 24 (but other communication techniques can be used in other embodiments) that sends the data through its system to a wireless data receiver and computer combination 26. The wireless system is one that can provide data transmission (e.g., digital phone systems, such as GSM or CDMA which provide data transmission; two-way paging systems; one-way paging systems). This type of transfer can occur in accordance with the known technology described in the patents cited in the background explanation set forth above, which are incorporated herein by reference. In this implementation, the recipient computer 20 can be located at a data communication service provider facility. This computer 20 is connected to communicate with the Internet 4, and it is programmed to receive at least part of the responsive signals transmitted over the Internet and to transmit in response thereto end user signals from the recipient computer 20 to an end user. The end user signals are a representation of the human body function or condition (or other data) as initially sensed by the sensor 8.

[0049] As mentioned above, the log-on site 6 a and the recipient site 6 b can be combined such that a single site provides both the medical data acquisition and transmission program to be downloaded to the patient site and also the programming necessary to receive data and directly display it or transfer it by other means to an end user. This is illustrated in FIG. 8 which is described below.

[0050] The method of the present invention for the foregoing embodiments is now described with reference to FIG. 6. Using the computer 10 (e.g., personal computer 16) where the patient is located, the Internet site having the medical data acquisition and transmission program is accessed from the respective patient's site. Access occurs by the computer 10 at the patient site 2 running its web browser or otherwise gaining access to the Internet 4. Once Internet access has been gained, the computer 10 accesses the known log-on site where the medical data acquisition and transmission program is located. This can be the site through which Internet access is gained, or the site containing the medical data acquisition and transmission program can be different from the Internet portal.

[0051] When the appropriate site has been accessed, identification information (e.g., name, password) is entered (e.g., manually by the patient or automatically by preprogramming the patient's computer 10) to enable the computer 10 at the patient site 2 to select and download the medical data acquisition and transmission program. When the appropriate information has been given to the log-on site 6 a in the illustrated embodiments of FIGS. 3 and 4, the medical data acquisition and transmission program downloads to, or otherwise becomes accessible for use by, the computer 10 at the patient site 2.

[0052] The medical data acquisition and transmission program preferably downloads in active mode so that it is ready to perform the functions at the patient site 2 without the patient having to perform functions other than manipulating the sensor to sense the biological function or condition to be transmitted to the computer 10 at the patient site. In the implementation of FIG. 5, the patient 12 holds the heart monitor 8 against his or her chest. The heart monitor 8 responds to the beating heart by generating corresponding acoustical signals transmitted to the microphone 14. The microphone 14 converts the signals to analog electric signals communicated to the personal computer 16 programmed with the downloaded medical data acquisition and transmission program. In using the medical data acquisition and transmission program, the personal computer 16 receives the signals, processes the received signals, and transmits medical data signals onto the Internet 4 in response.

[0053] Transmitting over the Internet 4 to a recipient site 6 b includes accessing a second Internet site from the computer 10 and transmitting patient data over the Internet to the accessed second Internet site 6 b, both under control of the medical data acquisition and transmission program used by the computer 10.

[0054] In addition to the foregoing which is illustrated in FIG. 6, the method of the present invention can further comprise receiving, over the Internet, the transmitted medical data signals at a recipient site. As explained above, this can be any suitable site, such as a medical facility or a data communication service provider facility. The former would typically be an end user of the data whereas the latter would typically be an intermediary that transfers the data onto an end user. As explained above with reference to FIG. 4, such could include transmitting, from the data communication server provider facility to an end user, end user signals responsive to the received medical data signals. In FIG. 4, this includes transmitting the medical data signals through the wireless data network 24.

[0055] When the data is received by the end user, a display is generated in response to a received signal. The display is a representation of the transduced human body function or condition. In the example of a heart monitor implementing the sensor 8, the display can be a waveform corresponding to the detected heart beat energy. This display can be by way of a computer monitor or printer or other display device.

[0056] The method of the present invention can also be defined as comprising distributing by any suitable means (e.g., through direct mail, at physicians' offices) at least one sensor to each patient. For example, this includes distributing to a plurality of patients respective portable heart monitoring devices that the respective patients can hold against their chests without assistance. Each sensor can be marked with indicia defining the address of the Internet site of the log-on site 6 a. In the preferred embodiments of FIGS. 3 and 4, this can include the World Wide Web address for the respective log-on site 6 a that one or more of the patients is to access to download the medical data acquisition and transmission program.

[0057] The method further comprises maintaining the medical data acquisition and transmission program at the Internet site which is accessible by the patients such that the patients can use the medical data acquisition and transmission program to control their respective computers to receive and process signals from the patients' respective sensors and to transmit medical data onto the Internet in response. Use of the program is made by accessing the Internet site using a web browser program or other suitable programming loaded on the computer at the location of the patient.

[0058] Maintaining the medical data acquisition and transmission program includes storing the program in a computer at the Internet site 6 a for FIGS. 3 and 4. The method can further comprise storing in the computer at the Internet site a database of potential recipients of the medical data, wherein the database is also accessible by each patient such that each patient can identify from the potential recipients at least one selected recipient to receive the medical data for that patient.

[0059] One way of getting the information to the end users is to distribute to a plurality of physicians receivers for receiving at least portions of the medical data transmitted over the Internet. The receivers (which may also have transmitting capability can include pagers connected to palmtop computers such as referred to in the patents mentioned in the background portion of this specification and incorporated herein by reference.

[0060] A more detailed explanation of the foregoing will next be given with reference to FIGS. 7-10.

[0061]FIG. 7 shows a particular implementation of the components generally identified in FIG. 5. This includes the sensor 8, the microphone 14 and the personal computer 16. The sensor 8 provides an acoustical signal in simultaneous response to a biological function or condition, namely a beating heart in the illustrated implementation. The microphone 14 is located with the sensor 8 to receive the acoustical signal as the signal is provided from the sensor 8. The personal computer 16 is connected to-the microphone and is adapted to transfer over the Internet, in real-time response to the microphone receiving the acoustical signal, electric signals representative of the received acoustical signal.

[0062] The sensor 8 of the FIG. 7 implementation includes a device 30 that converts the beating heart energy into electric signals. This can be by conventional electrocardiogram (ECG) electrodes as represented in FIG. 7 or other suitable device (e.g., a suitable piezoelectric device). The electric signals generated by the device 30 are amplified through an amplifier 32. The output of the amplifier 32 is processed through a voltage to frequency converter 34 to provide an alternating current signal that drives a speaker 36 to emit the acoustical signal directed toward the microphone 14.

[0063] The microphone 14 converts the acoustical signal into an analog electric signal conducted through a connected microphone jack 38 of the personal computer 16. The microphone jack 38 connects to the input of an analog to digital converter circuit 40 in the personal computer 16. The circuit 40 interfaces the microphone to the computer by converting the analog signal from the microphone 14 to a digital signal that can be used by a microprocessor circuit 42. The microprocessor circuit 42 is connected to a mouse 44, a keyboard 46, and a display 48. The microprocessor circuit 42 also communicates with an Internet connection 50, such as a coupling to a telephone line (e.g., a modem).

[0064] The analog to digital converter circuit 40 preferably provides at least eight-bit resolution and samples at 8,000 or more samples per second; this can be implemented with conventional technology, one example of which is a SOUND BLASTER brand sound card from Creative Labs. The microprocessor circuit 42 is also conventional, but preferably has a microprocessor nominally rated at least 20 megahertz. It also includes suitable memory for program storage and processing functions (e.g., typically both read only memory and random access memory). The programming of such memory is also conventional prior to loading the medical data acquisition and transmission program referred to above. This conventional programming can include, for example, a Windows operating system and a compatible web browser, such as Microsoft's INTERNET EXPLORER program or Netscape's NAVIGATOR program. Thus, the foregoing, and the other components illustrated in FIG. 7, can be implemented with conventional devices.

[0065] With the equipment of FIG. 7, shown in simplified blocks in FIG. 8, a patient at the site of the sensor 8, microphone 14 and personal computer 16 uses the web browser stored in the personal computer 16 to log onto the Internet. Typically this occurs through whatever Internet service provider the patient uses. Non-limiting examples include America On-Line and AT&T Worldnet. Once logged onto the Internet through his or her Internet service provider, the patient types in the address of the Internet site containing the medical data acquisition and transmission program if it is not available at the Internet service provider site. For example, for the illustration in FIG. 8, the patient enters the address “http//www.datacritical.com”. The home page of datacritical.com appears on the display 48 of the personal computer 16. Through this home page, the patient downloads or otherwise accesses the medical data acquisition and transmission program. In one example, the datacritical.com home page contains a link that the patient selects to go to a page from which the program is downloaded. This linked page includes patient identification entry spaces, such as calling for the patient's unique password, to enable only a registered patient to download the program if such registry is desired to permit someone to download the program.

[0066] An example of a medical data acquisition and transmission program that is downloaded to the patient's computer 16 is appended at the end of this specification. The program can be of any suitable language and type to enable the implementation of the present invention; however, non-limiting examples include a program in ActiveX, or Java, or a plug-in module. Preferably the program is downloaded in an active mode so that the patient does not need to do anything to actuate the program to receive the medical data and to process it for display locally or transmission onto the Internet.

[0067] With the downloaded program running, an acoustical signal is generated at the patient site in response to the biological function or condition of the individual being monitored. This includes the heart monitor for the illustration of FIGS. 7 and 8. The acoustical signal is received in the microphone 14 near the individual. The microphone 14 converts the acoustical signal into an analog electric signal. The analog electric signal is communicated from the microphone 14 to the analog to digital conversion circuit 40 where it is converted to a digital electric signal. The digital electric signal is processed in the microprocessor circuit 42 of the personal computer 16 under control of the medical data acquisition and transmission program that has been downloaded and is actively running. The program can then display to the individual, through the display 48 of the personal computer 16, a representation of the individual's biological function or condition in response to which the acoustical signal was generated. The medical data acquisition and transmission program can also transmit the data onto the Internet. In the illustration of FIG. 8, the data is transmitted to datacritical.com where it can be stored in a searchable database and transmitted to an end user. Preferably the transmission to the end user occurs contemporaneously with the foregoing steps so that there is real-time transmission of the data from the time the biological function or condition of the individual was sensed to the time it is received by the end user. Thus, once the program has been downloaded into the personal computer 16, preferably the subsequent steps from sensing the condition through display to the individual at the patient's site or transmission to an end user are performed together in real time. The data downloaded to the end user can also be stored in the end user's computer for later retrieval. Thus, the medical information derived from the sensing activity can be useful both for clinical purposes by an end user medical care provider as well as for providing self-knowledge benefits for the patient, such as in monitoring his or her own biological functions or conditions.

[0068] A flow diagram of a preferred embodiment of the medical data acquisition and transmission program downloaded to the personal computer 16 is illustrated in FIG. 9, and the source code is listed at the end of this specification before the claims. The program is an ActiveX control in combination with an HTML page which collects, filters and displays a user's ECG waveform and heart rate. When the control is embedded in a web page (e.g., at datacritical.com in the FIG. 8 illustration), it is downloaded to the user's personal computer 16 and configured to record ECG data from the microphone 14 connected to the personal computer 16 and to display the ECG waveform in the browser window of the personal computer 16. Configuration of the control is achieved via HTML “Param” tags. The control is embedded in an HTML page via the “Object” tag-this tells the browser to load the control. After the “Object” tag, and the name of the control, come the “Param” tags which provide values for named parameters of the control. For example, in the appended program listing there is a parameter called “Microphone_Not Server,” and the Param statement looks like:

[0069] <PARAM NAME=“Microphone_Not_Server” VALUE=“1”>

[0070] This statement initializes the control to listen for data at the microphone.

[0071] The browser-loaded control can also stream, across the Internet, demodulated ECG data to a redisplay server at a recipient site if one is available. The redisplay server also contains a control program (see FIG. 10 and program listing) that processes the forwarded ECG data.

[0072] When the program of FIG. 9 operates in the personal computer 16 at a patient's site, the data source in this implementation referring to FIGS. 7-10 is an 8 kilohertz pulse code modulated (PCM) ECG signal received from the analog to digital converter circuit 40 in response to the acoustical signal sensed by the microphone 14. When the control is contained in a redisplay server, the data source is demodulated ECG data that has been sent from the personal computer 16 over the Internet to the redisplay server (or from the personal computer to datacritical.com and then to the redisplay server via the Internet or a paging network, for example).

[0073] In the flow diagram of FIG. 9, once the control has been downloaded to the user's personal computer, it is configured via an HTML “Param” tag to collect data from the microphone as input through the analog to digital converter circuit 40 as explained above. The control may also be configured to connect to a redisplay server via another “Param” tag in the manner described above. The control then receives a “draw yourself” event from Windows. The first time this event is received, the control starts listening for data on the microphone.

[0074] Upon receiving data from the microphone, the control demodulates the data from 8 kilohertz pulse code modulated to 200 hertz ECG samples. If a redisplay server is specified, the control streams the new, demodulated samples to the server over the Internet.

[0075] Once the ECG data has been demodulated, it is filtered based upon the filtering level set in a “Param” tag on the web page. Filtering levels are as listed in FIG. 9 (low pass filter, high pass filter, derivative filter, square filter).

[0076] The most recent two seconds worth of filtered data is then scanned for QRS complexes to determine the user's heart rate in beats per minute. The software fires a Windows “my view has changed” event, which causes Windows to call the control's “OnDraw” routine such that “OnDraw” updates the screen 48 of the personal computer 16 with the latest ECG waveform and heart rate.

[0077] Referring to FIG. 10, in a redisplay server, the program enables the server to listen at a known port for incoming demodulated ECG samples from a remote site. When samples are received, the server passes them to the control's filtering routine as described above. The same chain of events then occurs: the samples are filtered, QRS detection is executed, and the waveform is updated to the remote server's window.

[0078] Thus, the present invention is well adapted to carry out the objects and attain the ends and advantages mentioned above as well as those inherent therein. While preferred embodiments of the invention have been described for the purpose of this disclosure, changes in the construction and arrangement of parts and the performance of steps can be made by those skilled in the art, which changes are encompassed within the spirit of this invention as defined by the appended claims. <!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.0 Trans- itional//EN”> <!-- saved from url=(0031)http://wtdemo/webecg/webecg.htm --> <HTML><HEAD><TITLE>YourHeart.com</TITLE> <META content=“text/html; charset=windows-1252” http-equiv=con- tent-Type> <META content=“MSHTML 5.00.2314.1000” name=GEN- ERATOR></HEAD> <BODY> <OBJECT classid=CLSID:E254A2C2-B470-11D2-8455-00104B05249c      codeBase=WebEcgControl.dll#Ver- sion=1,0,0,29 height=400 id=WebEcgl width=8 00>    <PARAM NAME=“Fore_Color” VALUE=“65280”>    <PARAM NAME=“Back_Color” VALUE=“0”>    <PARAM NAME=“Grid_Color” VALUE=“49152”>    <PARAM NAME=“Grid_Style” VALUE=“0”>    <PARAM NAME=“Max_mV” VALUE=“2”>    <PARAM NAME=“Min_mV” VALUE=“−2”>    <PARAM NAME=“Step_mV” VALUE=“0.5”>    <PARAM NAME=“Fore_Width” VALUE=“0”>    <PARAM NAME=“Display_Seconds” VALUE=“5”>    <PARAM NAME=“Volume_Threshold” VALUE=“1”>    <PARAM NAME=“Filtering” VALUE=“0”>    <PARAM NAME=“BPM_Font_Size” VALUE=“36”>    <PARAM NAME=“Detect_Percent” VALUE=“40.0”>    <PARAM NAME=“Server_Address” VAL-    UE=“209.131.191.103”>    <PARAM NAME=“Microphone_Not_Server” VALUE=“1”> </OBJECT> <BR>Web-ECG (SM) <BR>Copyright (C) 1999 <BR>Data Critical Corp. <BR>All Rights Re- served <BR>US Patent 5735285 <BR>US and International Patents Pend- ing <BR>Play an acoustic ECG source into your computer microphone. You may have to adjust the gain setting before you begin to see your ECG. <BR></BODY></HTML> // DemodEcg.h #include “stdafx.h” void DemodECG ( short ECGdata[],   int    *nECGsam- ples, LPSTR WAVdata, int    nWAVsamples, int    iPeriodsize, int iCtrOff- set ); int FrequencyEstimate(HWND hwnd, LPSTR lpsWavDa- taIn, int iWavDataSize, int iPeri od); /*********************************************************** DemodECG.C Project Rhythm Stat XLS for CE Palm PC 14 Aug 98  ***********************************************************/ #include “stdafx.h” #include “DemodEcg.h” //#include <windows.h> //#include <windowsx.h> #include <math.h> //#include “RStat_XLS.h” #define RESIDUE_DIVISOR 2 #define WINDOW_SIZE 4 #define MIN_HEIGHT    40 #define MIN_COUNT    20 #define TOLERENCE     10 TCHAR tcInfo[]; static HANDLE OpenWavFile  ( HWND hwnd, LPSTR lpszFileName ); static long ValidWavFile        ( HWND hwnd, HANDLE hwavHan- dle, DWORD *dwSampFreq ); static void DemodOneSegECG( short outData[], short inDa- ta[], int iPeriodsize, i nt ,CtrOffset ); short ResidueEstimate(short iLastData, short iCurrentData, int iPer- iodsize); void GenerateArcTanTable(void); void CloseECGfile(void); +TL,1 BOOL OpenECGfile ( void ); // BOOL WriteECGdata ( short ECGsamp ); void CloseECGfile ( void ); short LPfilter ( short in ); // GLOBAL FOR RESIDUE ESTIMATE FUNCTION   short int iArcTan[20] [20];   unsigned long lNumsamples;   short int iBPFOut [2] = {0, 0};   short int iBPFFilt [2] = {0, 0};  short int iResidue   =   0;   short int iCycAccum [2] = {760, 0}; // for the demodulator, this must start a t 800 /*********************************************************** 80 in, 2 out. With 8000 Hz acoustic sampling, the output will be 200 Hz ECG. The CTR_OFFSET value is the cycle count that will be seen in 80 acoustic data points for a nominal 1900 Hz carrier for the FM modulated ECG.  ***********************************************************/ void DemodECG ( /*LPSTR*/ short ECGdata[],   int *nECGsam- ples, LPSTR WAVdata, int nWAvsamples, int iPeriodsize, int iCtrOffset ) { int i; int j, k; short inData   [120]; // temporary input buffer for demodulation short outData[ 2]; // output buffer for demodulated data short s1,s2; // initialize globals GenerateArcTanTable (); iCycAccum[0] = iCycAccum[1] =0; k = 0; // OpenECGfile(); for(i= 0; i<= nWAVsamples - iPeriodsize; i += iPeriodsize )     { for( j=0; j<iPeriodsize; j++ ) inData[j] = (short) WAVdata[i+j]; DemodOneSegECG(outData, inData, iPeriodsize, iCtrOffset); s1 = outData[0]; s2 = outData[1]); //   s1 = LPfilter(outData[0]);   s2 = LPfilter(outDa-   ta[1]); //WriteECGdata(s1);   WriteECGdata(s2); //   ECGdata[k++] =(char) s1;   ECGdata[k++] = (char) s2;      ECGdata[k++] = s1;   ECGdata[k++] = s2;     } *nECGsamples = k; // CloseECGfile(); } /*********************************************************** 80 in, 2 out  **********************************************************/ static void DemodOneSegECG( short outData[], short inData[],  int iPeriodsize, it iCtrOffset ) {   int j; short iDatum;   iResidue = 0;   for( j=0; j < iPeriodsize/2; j++ ) { // SAVE LAST BPFOut FOR RESIDUE ESTIMATOR iBPFOut[1] = iBPFOut[0]; iDatum = inData[j]; // BAND PASS FILTER  iBPFOut [0] = iDatum − iBPFFilt[0];  iBPFFilt[0] = iBPFFilt[1];  iBPFFilt[1] =] iDatum + (iBPFOut[0] / 2);  // CHECK FOR COMPLETE CYCYLE  if((iBPFOut[1] < 0) && (iBPFOut[0] >= 0)) {  iCycAccum[0] += iPeriodsize;  iCycAccum[1] += iPeriodsize; } }   // ADD RESIDUE ESTIMATE TO iCycAccum[0]   iResidue = ResidueEstimate(iBPFout[1], iBPFOut[0], iPeriodsize);   iCycAccum[0] += iResidue;   // SUBTACT OFFSET AND SCALE THE OUTPUT   iCycAccum[0] −= iCtrOffset;   iCycAccum[0] /= 2;   // CHECK FOR OUT OF BOUND CONDITIONS   if(iCycAccum[0] >   127) iCycAccum[0] = 127;   if(iCycAccum[0] < −128) iCycAccum[0] = −128;   // OUTPUT FIRST DATA POINT   // fpnintf(output, “%d\n”, iCycAccum[0]);   outData[0]= iCycAccum[0];   // RESET iCycAccum[0]   iCycAccum[0] = −iResidue; //   for( j=40; j<80; j++ )      for (j=(iPeriodsize/2); j<iPeriodsize; j++)      { // SAVE LAST BPFOut FOR RESIDUE ESTIMATOR iBPFOut[1] = iBPFOut[0]; // ALAW DECODE // iDatum = iAlawToInt[(cAlawData[] + 128)]; iDatum = inData[j]; // BAND PASS FILTER iBPFOut[0] = iDatum − iBPFFilt[0]; iBPFFilt[0] = iBPFFilt[1]; iBPFFilt[1] = iDatum + (iBPFout[0] / 2); // CHECK FOR COMPLETE CYCYLE if((iBPFOut[1] < 0) && (iBPFOut[0] >=0))   { iCycAccum[0] += iPeriodsize; iCycAccum[1] += iPeriodsize;   }     }   // ADD RESIDUE ESTIMATE TO iCycAccum[0]   iResidue =ResidueEstimate(iBPFout[1], iBPFOut[0], iPeriodsize);   iCycAccum[1] += iResidue;   // SUBTRACT OFFSET AND SCALE THE OUTPUT   iCycAccum[1] −= iCtrOffset;   iCycAccum[1] /= 2;   // CHECK FOR OUT OF BOUND CONDITIONS   if (iCycAccum[1] > 127) iCycAccum[1] = 127;   if (iCycAccum[1] < −128) iCycAccum[1] = −128;   // OUTPUT FIRST DATA POINTb   outData[1]= iCycAccum[1];   // RESET iCycAccum[0]   iCycAccumfi]=-iResidue; } /***********************************************************  ***********************************************************/ short ResidueEstimate(short iLastData, short iCurrentData, int iPeriodsize) {   if( iLastData <= 0)      { iLastData = −iLastData;       if( iCurrentData < 0)       { iCurrentData = −iCurrentData; while((iLastData>=19) || (iCurrentData>=19)) { iCurrentData /= 2;  iLastData /= 2; } return(i Periodsize - iArcTan [(iCurrentData)] [(i LastData)]);       }     else { while((iLastData>=19) II (iCurrentData>=19)) { iCurrentData /= 2;   iLastData /= 2; }  return(iArcTan[(icurrentData)][(iLastData)]); }    else if (iLastData >= 0)       { if( iCurrentData > 0) { while((iLastData>=19) || (iCurrentData>=19)) { iCurrentData /= 2;   iLastData /= 2; }   return(i Periodsize/2 - iArcTanjXiCurrentData)J E(iLastData))); }       else { iCurrentData =-iCurrentData;    while((iLastData>=19) || (iCurrentData>=19)) { iCurrentData /= 2;    iLastData /= 2; }   return(i Periodsize/2 + iArcTan[(iCurrentData))[ ](iLastData)]); }       else return(0); } /***********************************************************  ***********************************************************/ void GenerateArcTanTable(void) { int i, j;    double dPhase;    double PI = 3.1415927;    double id,jd;    for( i=0; i<20; i++)   {for( j=0; j<20; j++) { id = (double) i;    jd = (double) j;    if (j==0) dPhase = PI/2.0;    else       dPhase =atan2( id, jd);    iArcTan[i] [j] = (short)((80.0*dphase/(2.0*PI)) + 0.5); }   } } /*********************************************************** HANDLE hECGfile; /***********************************************************  ***********************************************************/ BOOL WriteECGdata( short ECGsamp ) { int n;    DWORD dwNumWritten;    TCHAR strECGsamp[80];    n = wsprintf(strECGsamp,TEXT( “%d\n” ), ECGsamp);    if(!writeFile(hECGfile, strECGsamp,n, &dwNumwritten, NULL)) { CloseHandle(hECGfile);    return −1; }    return( TRUE ); } /***********************************************************  ***********************************************************/ BOOL Open ECGfile (void) {   hECGfile= CreateFile( TEXT(“\\ECGfile.xls”), GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);   if (hECGfile==INVALID_HANDLE_VALUE) {CloseHandle(hECGfile);   return( FALSE ); }   return( TRUE); } /***********************************************************  ***********************************************************/ void CloseECGfile (void) { CloseHandle(hECGfile); } /***********************************************************  ***********************************************************/ short LPfilter(short in) {   static short y1 = 0, y2=0, x[26], n=12;   short y0;   x[n] = x[n+13] = in;   y0 = (y1 << 1) − y2 +x[n] − ( x[n+6] <<1) + x[n+12];   y2=y1;   y1=y0;   y0 >>= 5;   if(--n < 0 ) n = 12;   return (y0); } /***********************************************************  ************************************************************/ int AverageVector( LPSTR lpData, int iNsamples) { int i = 0;   long lAvgY=0;   for (i=0;i<iNsamples;i++) lAvgY += ((long)lpData[i];   return ((int)(lAvgY/(long)iNsamples)); } /***********************************************************  ***********************************************************/      Estimates the cycles in an acoustic array of iWavDataSize      Assumes acoustic sampling rate of 8KHZ. Assume the ATan table      made before we get here. Reason is to avoid the time loss where we      are making multiple calls to the frequency estimator. /***********************************************************  ***********************************************************/ int FrequencyEstimate( HWND hwnd, LPSTR lpsWavDataIn, int iWavDataSize, int iPeri odsize ) { int i,j,in;   short sWavData[92];   short iDatum;   long lFcAccumulated =   0;   int iCycleAccumulator =   0;   int iOut[2] = { 0, 0};   int iBpf[2] = { 0, 0};   int iFcOffset =   0;   int iHz =   0;   Array transfer control & counters   INT iSegmentsize =  iPeriodsize+4;   INT iPeriods =  0;   // initialize globals   // Assume this call to build the ATan table made before we get here   // GenerateArcTanTable ( );   iResidue = −1;   /**** Read And Demodulate .WVE Data *********/   in=0;   while ((in + iSegmentsize) < iWavDataSize )     { // Get iPeriodsize samples for( 1=0; i<iSegmentsize; i++)sWavData[i] = (short)lpsWavDataIn[in++]; iPeriods ++; //INITIALIZE THE CYCLE ACCUMULATOR iCycleAccumulator = 0; // DO FIRST iDatum = sWavData[0]−128; iout[0] = iDatum − iBpf[0]; iBpf[0] = iBpf[1]; iBpf[1] =i Datum + (iOut[0] / RESIDUE_DIVISOR); // DO SECOND iDatum = sWavData[1]−128; iOut[0] = iDatum − iBpf[0]; iBpf[0] = iBpf[1]; iBpf[1] = iDatum + (iOut[0] / RESIDUE_DIVISOR); // DO THIRD ( LAST DATA POINT FOR RESIDUE ESTIMATE ) iDatum = sWavData[2]−128; iOut[1] = iDatum − iBpf[0]; iBpf[0] = iBpf[1]; iBpf[1] = iDatum + (iout[1] / RESIDUE_DIVISOR); // DO FOURTH iDatum = sWavData[3]−128; iout[0] = iDatum − iBpf[0]; iBpf[0] = iBpf[1]; iBpf[1] = iDatum + (iOut[0] / RESIDUE_DIVISOR); // FIND RESIDUE ESTIMATE AT BEGINNING OF WINDOW iResidue   = ResidueEstimate(iOut[1], iOut[0],      iPeriodsize); iCycleAccumulator += (iPeriodsize − iResidue); // CYCLE COUNT iPeriodsize POINT SAMPLE for(j=4; j<iSegmentsize; j++)     { // SAVE LAST BPFOut FOR RESIDUE ESTIMATOR AND CYCLE COUNTER iOut[1] = iOut[0]; iDatum = sWavData[j]−128; // BAND PASS FILTER iOut[0] = iDatum − iBpf[0]; iBpf[0] = iBpf[1]; iBpf[1] = iDatum + (iOut[0] / RESIDUE_DIVISOR); // CHECK FOR COMPLETE CYCYLE if((iout[1] < 0) && (iOut[0] >= 0))   { iCycleAccumulator += iPeriodsize;   } } iResidue   =Resi dueEsti mate (i Out [1), jOut [0], i Periodsize); iCycleAccumulator += iResidue; lFcAccumulated += ((long)iCycleAccumulator);        }; // endwhile  if( iPeriods )        { i FcOffset = (int) ( 1 FcAccumulated / (long)iPeriods);        }  iHz = (int)((long)iFcOffset*100L)/(long)iPeriodsize; /* wsprintf( tclnfo, TEXTQ%i cy, %i,%i Sc pds”) , ((int)lFcAccumulated), iPeriodsiz e, iPeriods); SetWindowText( GetDlgItem(hwnd, IDC_DATETIME), tcInfo ); Updatewindow(GetDlgItem(hwnd, IDC_DATETIME)); Sleep(5000); */ //wsprintf( tcInfo, TEXT(“OffSet %i, Est %i Hz ”) , iFcOffset, iHz ); //wsprintf( tcInfo, TEXT(“Estimate %i Hz ”) , iHz ) //SetWindowText( GetDlgItem(hwnd, IDC_DATETIME), tcInfo ); //UpdateWindow(GetDlgItem(hwnd, IDC_DATETIME)); return iFcOffset; } // network.h unsigned long GetAddr (LPSTR szHost); /* network.cpp Taken from “Windows Sockets Network Programming” by Bob Quinn and Dave Shute, Addison Wesley ISBN 0-201-63372-8 */ #include “stdafx.h” unsigned long GetAddr(LPSTR szHost) {       LPHOSTENT lpstHost;       unsigned long lAddr = INADDR_ANY;       // check that we have a string       if (*szHost)       { // check for a dotted-Ip address string lAddr = inet_addr(szHost); // if not an address, then try to resolve it as a host name if ((lAddr ==INADDRNONE) &&   (strcmp(szHost, “255.255.255.255”))) {   lpstHost = gethostbyname(szHost);   if (lpstHost)   { // success lAddr = *((unsigned long FAR *) (lpstHost->h_addr));   }   else   { // failure lAddr = INADDR_ANY;   } }       }       return(lAddr); } // QRS.h #include <math.h> // Apply these in this order to each point of your data to // filter and mangle it into big blocky mesas where the // QRSs are... extern short LowPassFilter(short data); extern short HighPassFilter(short data); extern short Derivative(short data); #define Square(x) ((x) * (x)) //#define square(x) (fabs((x))) //#define Square(x) (−(x)) extern short MovingWindowIntegral (short data); // Single line equivalent of: // if (x > 127) //   return 127; // else if (x < −128) //   return −128; // else //   return (signed char)(x); /* #define CharFilter(x) ( (Cx) > 127) ?   127 : ((x) < −128) ? −128 : (x)) */ #define CharFilter(x) ((signed char)( ((x) > 127) ? 127 : ((x) < −128) ? −128 :  (x))) //QRS.cpp #include “stdafx.h” #include “QRS.h” // Taken from “Biomedical Digital Signal Processing”, chapter 12.5, // starting on page 246. // // Information was faxed to me, and I do not know any further // reference details. // // Original algorithm was designed for a microcontroller, and uses // shifting to a proximate multiplies and divides. If MICROCONTROLLER // is defined, the following code will work as in the original (with // shifts). If not defined, the following code will multiply or // divide by the actual amount needed to compensate for the filters // (which *should* result in a cleaner result). #define MICROCONTROLLER short LowPassPilter(short data) { static short y1=0, y2=0, x[26], n=12; short y0; x[n] =x[n+13] =data; y0 =(y1<<1) − y2 + x[n] − (x[n+6] <<1) + x[n+12]; y2 = y1; y1 = y0; #ifdef MICROCONTROLLER y0 >>=5; #else y0 /= 36; #endif if (--n < 0) n = 12; return (y0); } short HighPassPilter(short data) { static short y1 = 0, x[66], n=32; short y0; x[n] = x[n+33] = data; y0 = yl + x[n] − x[n+32]; y1 = y0; if(--n < 0) n=32; // Actually has gain of 32, according to page 250... return(x[n+16] − (y0 >> 5)); } short Derivative(short data) { short y, i; static short x_derv[4]; // 1/8 is approximation to actual gain of 1/10 /*y = 1/8 (2x(nT) + x(nT − T) − x(nT − 3T) − 2x(nT − 4T))*/ y = (data << 1) + x_derv[3] − xderv[1] − (x_derv[0] << 1); #ifdef MICROCONTROLLER y >>=3; #else y /= 10; #endif for (i=0; i<3; i++) x_derv[i] = x_derv[i+1]; x_derv[3] = data; return(y); } // Already defined in QRS.h // Just here as a reminder that you have to Square the results of // the differentiation before you integrate... // #define square(x) ((x) * (x)) short MovingWindowIntegral(short data) { static short x[32], ptr = 0; static long sum = 0; long ly; short y; if (++ptr == 32) ptr = 0; sum −= x[ptr]; sum += data; x[ptr] = data; // unknown if this is correct or an approximation... // Seems to be taking the average point value, and since // our window is 32 wide, you would divide by 32, but I // do not guarantee that that is a correct interpretation. ly = sum >> 5; if(ly > 32400) /*check for register overflow*/ y = 32400; else y = (short) ly; return(y); WSACleanup(); // MessageBox(“WebECG Stopping”, “Informational message...”, MB_OK); } DECLARE_REGISTRY_RESOURCEID(IDR_WEBECG) BEGIN_COM_YAP (cWebEcg) COM_INTERFACE_ENTRY (IWebEcg) COM_INTERFACE_ENTRY (IDispatch) COM_INTERFACE_ENTRY_IMPL (IViewObjectEx) COM_INTERFACE_ENTRY_IMPL_IID(IID_IViewObject2, IViewObjectEx) COM_INTERFACE_ENTRY_IMPL_IID(IID_IViewObject, IViewObjectEx) COM_INTERFACE_ENTRY_IMPL (IOleInPlaceObjectWindowless) COM_INTERFACE_ENTRY_IMPL_IID(IID_IOleInPlaceObject, IOleInPlaceObjectWindowle ss) COM_INTERFACE_ENTRY_IMPL_IID(IID_IOleWindow, IOleInPlaceObjectWindowless) COM_INTERFACE_ENTRY_IMPL (IOleinPlaceActiveObject) COM_INTERFACE_ENTRY_IMPL(IOleControl) COM_INTERFACE_ENTRY_IMPL(IOleObject) COM_INTERFACE_ENTRY_IMPL(IQuickActivate) COM_INTERFACE_ENTRY_IMPL(IPersistPropertyBag) COM_INTERFACE_ENTRY_IMPL(IPersistStorage) COM_INTERFACE_ENTRY_IMPL(IPersistStreamInit) COM_INTERFACE_ENTRY_IMPL(ISpecifyPropertyPages) COM_INTERFACE_ENTRY_IMPL(IDataobject) COM_INTERFACE_ENTRY(IProvideClassInfo) COM_INTERFACE_ENTRY(IProvideClassInfo2) COM_INTERFACE_ENTRY(ISupportErrorInfo) COM_INTERFACE_ENTRY_IMPL(IConnectionPointContainer) END_COM_MAP() BEGIN_PROPERTY_MAP(CWebEcg) // Example entries // PROP_ENTRY(“Property Description”, dispid, clsid) PROP_EN- DISPID_FORE- CLSID_StockCol- TRY(“Fore_Color”, COLOR, orPage) PROP_EN- DISPID_BACK- CLSID_StockCol- TRY(“Back_Color”, COLOR, orPage) PROP_EN- dispidGridColor, CLSID_WebEcgPPG) TRY(“Grid_Color”, PROP_EN- dispidGridStyle, CLSID_WebEcgPPG) TRY(“Grid_Style”, PROP_EN- dispidMaxmV, CLSID_WebEcgPPG) TRY(“MaxmV”, PROP_EN- dispidMinmV, CLSID_WebEcgPPG) TRY(“MinmV”, PROP_EN- dispidStepmV, CLSID_WebEcgPPG) TRY(“Step_mV”, PROP_EN- dispidForeWidth, CLSID_WebEcgPPG) TRY(“Fore_Width”, PROP_EN- dispidDis- CLSID_WebEcgPPG) TRY(“Display_Se- playSeconds, conds”, PROP_EN- dispidVol- CLSID_WebEcgPPG) TRY(“Vol- umeThreshold, ume_Threshold”, PROP_EN- dispidFiltering, CLSID_WebEcgPPG) TRY(“Filtering”, PROP_EN- dispidBPMFont- CLSID_WebEcgPPG) TRY(“BPM_(—) Size, Font_Size”, PROP_EN- dispidDe- CLSID_WebEcgPPG) TRY(“Detect_Per- tectPercent, cent”, PROP_EN- dispidSer- CLSID_WebEcgPPG) TRY(“Server_Add- verAddress, ress”, PROP_ENTRY(“Microphone_Not_Server”, dispidMicrophoneNotSer- ver, CLSID_WebEcgPP G) PROP_EN- dispidAddNew- CLSID_WebEcgPPG) TRY(“AddNewPoint”, Point, END_PROPERTY_MAP() BEGIN_CONNECTION_POINT_MAP(CWebEcg) CONNECTION_POINT_ENTRY(IID_IPropertyNotifySink) END_CONNECTION_POINT_MAP() BEGIN_MSG_MAP (CwebEcg) MESSAGE_HANDLER(WM_PAINT, OnPaint) MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus) MESSAGE_HANDLER(WM_KILLFOCUS, OnKillFo- cus) MESSAGE_HANDLER(MM_WIM_OPEN, OnWimO- pen) MESSAGE_HANDLER(MM_WIM_DATA, OnWimDa- ta) MESSAGE_HANDLER(MM_WIM_CLOSE, OnWim- Close) MESSAGE_HANDLER(MM WOM_OPEN, OnWomO- pen) MESSAGE_HANDLER(MM WOM_DONE, OnWom- Done) MESSAGE_HANDLER(MM WOM_CLOSE, OnWom- Close) MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLBut- tonDown) END_MSG_MAP () // ISupportsErrorlnfo STDMETHOD(InterfaceSupportsErrorInfo) (REFIID riid); // IViewObjectEx STDMETHOD(GetViewStatus) (DWORD* pdwStatus) { ATLTRACE(_T(“IViewObjectExImpl::GetViewStatus\n”)); *pdwStatus = VIEWSTATUS_SOLIDBKGND | VIEWSTATUS_OPAQUE; return S_OK; } // IWebEcg public: STDMETHOD(get_AddNewPoint)(/* [out, retval]*/ short *pVal); STDMETHOD(put_AddNewPoint)(/*[in]*/ short newVal); STDMETHOD(get_MicrophoneNotServer)(/*[out, retval]*/ short *pVal); STDMETHOD(put_MicrophoneNotServer)(/*[in]*/ short newVal); STDMETHOD(get_ServerAddress)(/*[out, retval]*/ BSTR *pVal); STDMETHOD(put_ServerAddress)(/*[in]*/ BSTR newVal); STDMETHOD(get_DetectPercent)(/*[out, retval]*/ double *pVal); STDMETHOD(put_DetectPercent)(/*[in]*/ double newVal); STDMETHOD(get_BPMFontSize)(/*[out, retval]*/ short *pVal); STDMETHOD(put_BPMFontSize)(/*[in]*/ short newVal); STDMETHOD(get_Filtering)(/*[out, retval]*/ short *pVal); STDMETHOD(put_Filtering)(/*[in]*/ short newVal); STDMETHOD(get_VolumeThreshold)(/*[out, retval)*/ short *pVal); STDMETHOD(put_VolumeThreshold)(/*[in]*/ short newVal); STDMETHOD(get_DisplaySeconds)(/*[out, retval]*/ short *pVal); STDMETHOD(put_DisplaySeconds)(/*[in]*/ short newVal); STDMETHOD(get_ForeWidth)(/*[out, retval]*/ long *pVal); STDMETHOD(put_ForeWidth)(/*[in]*/ long newVal); STDMETHOD(get_StepmV)(/*[out, retval]*/ double *pVal); STDMETHOD(put_StepmV)(/*[in)*/ double newVal); STDMETNOD(get_MaxmV)(/*[out, retval]*/ double *pVal); STDMETHOD(put_MaxmV)(/*[in]*/ double newVal); STDMETHOD(get_MinmV)(/*[out, retval]*/ double *pVal); STDMETHOD(put_MinmV)(/*[in]*/ double newVal); STDMETHOD(get_GridStyle)(/* [out, retval]*/ long *pVal); STDMETHOD(put_GridStyle)(/* [in]*/ long newVal); STDMETHOD(get_GridColor)(/* [out, retval]*/ OLE_COLOR *pVal); STDMETHOD(put_GridColor)(/*[in]*/ OLE_COLOR newVal); HRESULT FireviewChange(); HRESULT OnDraw(ATL_DRAWINFO& di); HRESULT OnWimOpen(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); HRESULT OnWimData(UINT uMsg, WPARAM wParam, LPARAM lparam, BOOL& bHandled); HRESULT OnWimClose(UINT uMsg, WPARAM wparam, LPARAM lparam, BOOL& bHandled); HRESULT OnWomOpen(UINT uMsg, WPARAM wParam, LPARAM iParam, BOOL& bHandled); HRESULT OnWomDone(UINT uMsg, WPARAM wparam, LPARAM iparam, BOOL& bHandled); HRESULT OnwomClose(UINT uMsg, WPARAM wParam, LPARAM iparam, BOOL& bHandled); LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM iparam, BOOL& bHandled ); CComPtr<IPictureDisp> m_pMouseIcon; OLE_COLOR m_clrBackColor; OLE_COLOR m_clrBorderColor; OLE_COLOR m_clrForeColor; OLE_COLOR m_clrGridColor; BOOL m_bBordervisible; long m_nBorderStyle; long m_nBorderWidth; long m_nForeWidth; long m_nGridStyle; long m_nMousePointer; short m_nvolumeThreshold; short m_nFiltering; short m_nBPMFontSize; CComBSTR m_bstrFileName; CComBSTR m_bstrServerAddress; bool m_bConnectedToServer; double m_dMinmV; double m_dMaxmv; double m_dStepmV; double m_dDetectPercent; // Shouldn't be public, but I don't care about nice now as // much as getting something working BOOL m_bMicrophoneNotServer; signed char * m_pCardioGram; unsigned char * m_pVerticalBar; POINT * m_pCardioGramPoints; unsigned long m_dwCardioGramLength; bool m_bRecording; unsigned char * m_pWavBuf[4]; unsigned char * m_pFullVav; unsigned long m_dwFullVavLength; HWAVEIN m_hWaveIn; PWAVEHDR m_pWaveHdr[4]; unsigned short m_nBPM; unsigned long m_dwStartDawing; unsigned long m_dwStopDrawing; HBITMAP m_hbmpWorkBitmap; HDC m_hdcWorkDC; bool m_bFrozen; RECT m_rctFreezeButton; SOCKET m_sckLocalSocket; sockaddr_in m_sckadrRemoteSocketAddress; WSADATA m_wsaWSAData; private: void NewRawData(signed short * RawData, unsigned long NumData); void ConnectToServer(); }; #endif //_WEBECG_H_ //----------------------------------------------------------------------------------------- // File: DCObject.h Coypright 1999, Data Critical Corporation // Author: Rik Slaven // Contents: Class CDCObject Interface and operator << //----------------------------------------------------------------------------------------- #if !defined( AFX_DCOBJECT_H__7ABC83EA_259B_(—) 11D3_BE8D_000000000000_INCLUDED_) #define AFX_DCOBJECT_H_7ABC83EA_259B_11D3_(—) BE8D_000000000000_INCLUDED_(—) #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 //----------------------------------------------------------------------------------------- // Includes //----------------------------------------------------------------------------------------- #include <iostream> using namespace std; //----------------------------------------------------------------------------------------- // Class: CDCObject // Purpose: Base class for all Data Critical objects // Patterns: //----------------------------------------------------------------------------------------- class CDCObject { public: virtual ostream & DumpCntxt( ostream &os ) const = 0; protected CDCObject() {;} virtual ˜CDCObject() {;} {; // end of class CDCobject //----------------------------------------------------------------------------------------- // Function: operator << // Purpose: Inserts CDCObject into ostream //----------------------------------------------------------------------------------------- ostream & operator <<( ostream &os, const CDCobject &dco ); #endif // !defined ( AFX_DCOBJECT_H_7ABCS3EA_259B_(—) 11D3_BE8D_000000000000_INCLUD ED_ ) //----------------------------------------------------------------------------------------- // File: BPMDetector.h Coypright 1999, Data Critical Corporation // Author: Rik Slaven // Contents: Class CBPMDetector Interface //----------------------------------------------------------------------------------------- #if !defined( AFX_BPMDETECTOR_H_39618CF4_348F_(—) 11D3_BEAF_000000000000_INCLUDED_(—)  ) #define AFX_BPMDETECTOR_H_39618CF4_348F_(—) 11D3BEAF_000000000000__INCLUDED_(—) #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 //----------------------------------------------------------------------------------------- // Includes //----------------------------------------------------------------------------------------- #include “DCObject.h” //----------------------------------------------------------------------------------------- // Class: CBPMDetector // Purpose: Detect heart beats per minute in a stream of ECG data. // Patterns: //----------------------------------------------------------------------------------------- class CBPMDetector : public CDCObject { public: //------------------------------------------------------------ // Constructor, destructor //------------------------------------------------------------ CBPMDetector( int       samplesPerSec = 200, int       maxQRSPerSec =  4, int       secsToBuffer =  2, int       averageCount =  1, double detectPercent =  50.0 ); virtual ˜CBPMDetector(); //------------------------------------------------------------ // Virtual Overrides //------------------------------------------------------------ // From CDCObject virtual ostream & DumpCntxt( ostream &os ) const; //------------------------------------------------------------ // Accessors //------------------------------------------------------------ int AverageCount() { return mAverageCount; } int SamplesPerSec() { return mSamplesPerSec; } int SecsToBuffer() { return mSecsToBuffer; } double DetectPercent() { return mDetectPercent; } int AvgBPM() { return mAvgBPM; } int RawBPM() { return mCurBPM; } //------------------------------------------------------------ // Mutators //------------------------------------------------------------ void AverageCount( int avgCount ); void DetectPercent( double newVal ) { mDetectPer- cent = newVal; } int update( int newPoint ); protected: //------------------------------------------------------------ // Prevent copy, assignment //------------------------------------------------------------ CBPMDetector( const CBPMDetector &rhs ); CBPMDetector & operator =( const CBPMDetector &rhs ); private: //------------------------------------------------------------ // Members //------------------------------------------------------------ int mSamplesPerSec; int mCumuSamps; int mSecsToBuffer; int mAverageCount; int mCurrentAverage; int mPreviousPoint; // Data value of t - 1 int mSampsSinceQRS; // # Samples since QRS int mTimeoutSamps; // # Samples to let pass before zeroing double mDetectPercent; int mAvgBPM; // BPM averaged over mAver- ageCount int mCurBPM; // Unaveraged BPM int *mpBPMs; // Array of BPMs for averaging int *mpsamples; // Array of samples int mNumSamples; // Number of samples int mCurrentSamp; // Index of current sample // end of class CBPMDetector #endif // !defined ( AFX_BPMDETECOR_H_39618CF4_348F_(—) 11D3_BEAF_000000000000_INC LUDED_ ) //----------------------------------------------------------------------------------------- // File: BPMDetector.cpp Coypright 1999, Data Critical Corporation // Author: Rik Slaven // Contents: Class CBPMDetector Implementation // // Implementation notes: //----------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------- // Includes //----------------------------------------------------------------------------------------- #include  “stdafx.h” #include  <stdlib.h> #include  “BPMDetector.h” //----------------------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------------------- CBPMDetector::CBPMDetector( int samplesPerSec, int maxQRSPerSec, int secsToBuffer, int averageCount, double detectPercent ) : mSamplesPerSec( samplesPerSec ),   mCumuSamps( 0 ),   mTimeoutSamps( samplesPersec * 4 ),   mSecsToBuffer( secsToBuffer ),   mAverageCount( averageCount ),   mCurrentAverage( 0 ),   mPreviousPoint( 0 ),   mSampsSinceQRS( 0 ),   mDetectPercent( detectPercent ),   mAvgBPM( 0 ),   mCurBPM( 0 ),   mpBPMs( NULL ),   mpSamples( NULL ),   mNumSamples( 0 ),   mCurrentSamp( 0 ) //------------------------------------------------------------ // Create and initialize array containing // maximum values //------------------------------------------------------------ mNumSamples = mSamplesPerSec * mSecsToBuffer; mpSamples = new int[ mNumSamples ]; // This is probably quicker // than a call to memset for( int i = 0; i < mNumSamples; i++ ) *(mpSamples + i) = 0; mpBPMs = new int[ mAverageCount ]; for( i = 0; i < mAverageCount; i++ ) *(mpBpMs + i) = 0; { // end constructor //----------------------------------------------------------------------------------------- // Destructor //----------------------------------------------------------------------------------------- CBPMDetector::˜CBpMDetector() { if( mpSamples ) delete [] mpSamples; if( mpBPMs ) delete [] mpBPMs; { // end destructor //----------------------------------------------------------------------------------------- // DumpCntxt //----------------------------------------------------------------------------------------- ostream & CBPMDetector::DumpCntxt( ostream &os ) const { os << “Cur BPM  : “ << mCurBPM << endl << “Avg BPM   : “ << mAvgBPM << endl << “Avg over Secs: “ mAverageCount << endl; for( int i = 0; i << mAverageCount; i++ ) os << “Avg[” << i << “[  : ” << mpBPMs[i] << endl; os << “Detect Pct       : “ << mDetectPercent << endl; os << “Secs To Buf   : “ << mSecsToBuffer << endl; os << “Num Samples  : “ << mNumSamples << endl; os << “SampsPerSec     : “ << mSamplesPerSec << endl; os << “SampsSinceQRS: “ << mSampsSinceQRS << endl; os << “Timeout Samps : “ << mTimeoutSamps << endl; return Os; } // end of DumpCntxt //----------------------------------------------------------------------------------------- // AverageCount - Adjust num of seconds over which HR is averaged //----------------------------------------------------------------------------------------- void CBPMDetector::AverageCount( int avgCount ) { if( avgCount == mAverageCount || avgCount <= 0 ) return; int  minCount = _min( mAverageCount, avgCount ); int  *pNewAvgs = new int [avgCount]; for( int i = 0; i < avgCount; i++ ) *(pNewAvgs + i) = 0; if( mpBPMs ) { for( i = 0; i < minCount; i++ ) *(pNewAvgs + i) = *(mpBPMs + i); delete [] mpBPMs; } mpBPMs = pNewAvgs; mAverageCount = avgCount; mCurrentAverage = _min( mCurrentAverage, mAveragecount ); // end of AverageCount //----------------------------------------------------------------------------------------- // Update - Pass new point through detector //----------------------------------------------------------------------------------------- int CBPMDetector::Update( int newPoint ) { //------------------------------------------------------------ // factor the current point into // array of maxes //------------------------------------------------------------ int *pSamp = mpSamples + mcurrentSamp; *pSamp = newPoint; //------------------------------------------------------------ // find the current maximum in // order to caic threshold for // QRS detection //------------------------------------------------------------ int curMax = 0; for( int i = 0; i < mNumSamples; i++ ) curMax = _max( curMax, *(mpsamples + i) ); int detectThresh = (int)( curMax * mDetectPercent / 100.0 ); //------------------------------------------------------------ // detect QRS via threshold method //------------------------------------------------------------ if( mPreviousPoint < detectThresh && newPoint >= detectThresh && mSampsSinceQRS > mSamplesPerSec / 5 ) { // Calculate current BPM mCurBPM = (msamplesPersec * 60) / mSampsSinceQRS; mSampsSinceQRS = 0; }  end if threshold detected else ++mSampsSinceQRS; if( mSampsSinceQRS > mTimeoutSamps ) { mCurBPM = 0; mSampsSinceQRS = 0; } //------------------------------------------------------------ // Update averaged BPM // NOTE: works like a ring buffer //------------------------------------------------------------ if( mCumuSamps >= mSamplesPerSec ) { mCumuSamps = 0; // Store BPM in buffer of BPMs for // averaging *(mpBpMs + mCurrentAverage) = mCurBPM; ++mCurrentAverage; if( mCurrentAverage >= mAverageCount ) mCurrentAverage = 0; } else ++mCumuSamps; //------------------------------------------------------------ // Average BPM Calculation //------------------------------------------------------------ int total = 0; for( i = 0; i < mAverageCount; i++ ) total += *(mpBPMs + i); mAvgBPM = total / mAverageCount; if( !mAvgBPM ) mAvgBPM = mCurBPM; //------------------------------------------------------------ // update the “current samp” index // NOTE: works like a ring buffer //------------------------------------------------------------ ++mCurrentSamp; if( mCurrentSamp >= mNumSamples ) mCurrentSamp = 0; mPreviousPoint = newPoint; return mCurBPM; // end of Update // WebEcg.h : Declaration of the CWebEcg #ifndef _WEBECG_H_ #define _WEBECG_H_ #include “resource.h”  // main symbols #include “network. h” #include “BPMDetector. h” #include “ringbuf.h” const unsigned long SAMPLE_FREQ = 8000; const unsigned long DECIMATION = 40;  // 40 to 1 decimation const unsigned long ECG_FREQ = (SAMPLE_REQ / DECIMATION); const unsigned long FRAME_PER_SEC = 20; const unsigned long WORK_BUFFER_SIZE = (SAMPLE_FREQ / FRAMES_PER_SEC); //#define DISPLAY_SECONDS  5 ////////////////////////////////////////////////////////////////////////////////////////////////////////// // CWebEcg class ATL_NO_VTABLE CWebEcg public CComObjectRootEx<CComsingleThreadModel>, public CComCoClass<CwebEcg, &CLSID_WebEcg>, public CComControl<CwebEcg>, public CStockPropImpl<CWeb Ecg, IWebEcg, &IID_IWebEcg, &LIBID_WEBECGCONTROLLi b>, public IProvideClassInfo2Impl <&CLSID_WebEcg, NULL, &LIBID_WEBECGCONTROLLi b>, public IPersistPropertyBagImpl<CWebEcg>, public IPersistStreamInitImpl<CWebEcg>, public IPersistStorageImpl<CWebEcg>, public IQuickActivateImpl<CWebEcg>, public IOleControlImpl<CWebEcg>, public IOleObjectImpl<CWebEcg>, public IoleInPlaceActiveObjectImpl <CWebEcg>, public IViewObjectExImpl<CWebEcg>, public IoleInPlaceObjectWindowlessImpl<CWebEcg>, public IDataObjectImpl<CWebEcg>, public ISupportErrorInfo, public IConnectiOnPointContainerImpl<CWebEcg>, public IPropertyNotifySinkCP<CWebEcg>, public ISpecifyPropertyPagesImpl <CWebEcg> { public: CWebEcg() : mBPM( 200, // samps per sec  4, // maxQRSPerSec  2, // secs to buffer  3, // secs to average  40.0 )// detect thresh { // MessageBox(“WebECG Starting”, “Informational message...”, MB_OK); m_bMicrophoneNotServer = false; mbDrawECG = false; m_bFrozen = false; m_bWindowonly = TRUE; m_nVolumeThreshold = 8; m_nBPMFontSize = 36; m_dDetectPercent = 50.0; m_nBPM = m_nAvgBPM = 0; mAge = 30; mMaxHR = 220 − mAge; m_nForeWidth = 2; m_clrForeColor = RGB(0,0,255); m_clrBackColor = RGB(0,0,0); m_clrGridColor = RGB(0,192,0); m_dwCardioGramLength = ECG_FREQ*2; m_pCardioGram = new (signed char[m_dwCardioGramLength]); m_pCardioGramPoints = new(POINT[m_dwCardioGramLength]); m_pVerticalBar =new (unsigned char[m_dwCardioGramLength]); for (unsigned long i = 0; i<m_dwCardioGramLength; i++) { m_pCardioGram[i] = 0; m_pVerticalBar[i] = 0; } m_dwStartDrawing = 0; m_dwStopDrawing = 0; m_bRecording = false; mBPMSampleCount = 0; mSampsPerBPMPt = ECG_FREQ / 4; mCumSamps = 0; for( i = 0; i < 4; i++ ) { m_pwaveHdr[i] = new(WAVEHDR); m_pWavBuf[i] = NULL; } m_dMaxmV =   2.0; m_dMinmV = −2.0; m_dStepmV  =   0.5; m_hbmpWorkBitmap = NULL; m_hdcWorkDC = NULL; WSAStartup(0x0101, &m_wsaWSAData); // 0x0101 means Winsock 1.1 m_bConnectedToServer = false; mMaxPoints = 0; mPointWidth = 4; mMinPointWidth = 4; mSpaceWidth = 2; mCurPoint = 0; mPointsRead = 0; mPointsPerCyc = 0; mDrawingWid = 0; mXscale = 0.0; mYscale = 0.0; mXoffset = 0.0; mYoffset = 0.0; mHeight = 0; mWidth = 0; mPrevWidth = −1; mYmax = 0; mMaxHrY = 0; mMinHrY = 0; // end constructor ˜CWebEcg() { int i; waveInReset(m_hWaveIn); waveInClose(m_hWaveIn); // Sleep((1.0/SAMPLE_FREQ)*(2*WORK_BUFF- ER_SIZE)*1000); // Give system time to shut down whatever. // Sleep((1000/SAMPLE_FREQ)*(2*WORKBUFF- ER_SIZE)); // Give system time to s hut down whatever. // Sleep((1000*2*WORK_BUFFER_SIZE)/SAM- PLE_FREQ); // Give system time to s hut down whatever. // Sleep((2000*WORK_BUFFER_SIZE)/SAM- PLE_FREQ); // Give system time to shut down whatever. for (i=0; i<4; i++) { waveInUnprepareHeader(m_hWaveIn, m_pWaveHdr [i], sizeof(WAVEHDR)); delete m_pWaveHdr[i]; if( m_pWavBuf[i] delete m_pWavBuf[i]; } delete m_pVerticalBar; delete m_pCardioGramPoints; delete m_pCardioGram; if (m_hbmpWorkBitmap) DeleteObject(m_hbmpWorkBitmap); +L,5 if (m_hdcWorkDC) DeleteObject(m_hdcWorkDC); shutdown(m_sckLocalSocket, 1); closesocket(m_sckLocalSocket); WSACleanup(); // MessageBox(“WebECG Stopping”, “Informational message...”, MB_OK); } DECLARE_REGISTRY_RESOURCEID(IDR_WEBECG) BEGIN_COM_MAP(CWebEcg) COM_INTERFACE_ENTRY(IWebEcg) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY_IMPL(IViewObjectEx) COM_INTERFACE_ENTRY_IMPL_IID(IID_IViewOb- ject2, IViewObjectEx) COM_INTERFACE_ENTRY_IMPL_IID(IID_IViewOb- ject, IViewObjectEx) COM_INTERFACE_ENTRY_IMPL(IOleInPlaceOb- jectwindowless) COM_INTERFACE_ENTRY_IMPL_IID(IIDIOleInPlaceOb- ject, IoleInPlaceObjectWindowl ess) COM_INTERFACE_ENTRY_IMPL_IID(IIDIOleWindow, IOleInPlaceObjectWindowless) COM_INTERFACE_ENTRY_IMPL(IOleInPlaceActiveObject) COM_INTERFACE_ENTRY_IMPL(IOleControl) COMINTERFACE_ENTRY_IMPL(IOleObject) COM_INTERFACE_ENTRY_IMPL(IQuickActivate) COM_INTERFACE_ENTRY_IMPL(IPersistPropertyBag) COM_INTERFACE_ENTRY_IMPL(IPersistStorage) COM_INTERFACE_ENTRY_IMPL(IPersistStreamInit) COM_INTERFACE_ENTRY_IMPL(ISpecifyPropertyPages) COM_INTERFACE_ENTRY_IMPL(IDataObject) COM_INTERFACE_ENTRY(IProvideClassInfo) COM_INTERFACE_ENTRY(IProvideClassInfo2) COM_INTERFACE_ENTRY(ISupportErrorlnfo) COM_INTERFACE_ENTRY_IMPL(IConnectionPointCon- tainer) END_COM_MAP() BEGIN_PROPERTY_MAP(CWebEcg) // Example entries // PROP_ENTRY(“Property Description”, dispid, clsid) PROP_EN- DISPID_FORE- CLSID_StockCol- TRY(“Fore_Color”, COLOR, orPage) PROP_EN- DISPID_BACK- CLSID_StockCol- TRY(“Back_Color”, COLOR, orPage) PROP_EN- dispidGridColor, CLSID_WebEcgPPG) TRY(“Grid_Color”, PROP_EN- dispidGridStyle, CLSID_WebEcgPPG) TRY(“Grid_Style”, PROP_EN- dispidMaxmV, CLSID_WebEcgPPG) TRY(“MaxmV”, PROP_EN- dispidMinmV, CLSID_WebEcgPPG) TRY(“MinmV”, PROP_EN- dispidStepmV, CLSID_WebEcgPPG) TRY(“Step_mV”, PROP_EN- dispidForeWidth, CLSID_WebEcgPPG) TRY(“Fore_Width”, PROP_EN- dispidDis- CLSID_WebEcgPPG) TRY(“Display_Se- playSeconds, conds”, PROP_EN- dispidVol- CLSID_WebEcgPPG) TRY(“Vol- umeThreshold, ume_Threshold”, PROP_EN- dispidFiltering, CLSID_WebEcgPPG) TRY(“Filtering”, PROP_EN- dispidBPMFont- CLSID_WebEcgPPG) TRY(“BPM_(—) Size, Font_Size”, PROP_EN- dispidDe- CLSID_WebEcgPPG) TRY(“Detect_Per- tectPercent, cent”, PROP_EN- dispidSer- CLSID_WebEcgPPG) TRY(“Server_Add- verAddress, ress”, PROP_ENTRY(“Microphone_Not_Server”, dispidMicrophoneNotSer- ver, CLSID_WebEcgPP G) PROP_EN- dispidAddNew- CLSID_WebEcgPPG) TRY(“AddNewPoint”, Point, END_PROPERTY_MAP() BEGIN_CONNECTION_POINT_MAP(CWebEcg) CONNECTION_POINT_ENTRY(IID_IPropertyNotifySink) END_CONNECTION_POINT_MAP() BEGIN_MSG_MAP (CwebEcg) MESSAGE_HANDLER(WM_PAINT, OnPaint) MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus) MESSAGE_HANDLER(WM_KILLFOCUS, OnKillFo- cus) MESSAGE_HANDLER(MM_WIM_OPEN, OnWimO- pen) MESSAGE_HANDLER(MM_WIM_DATA, OnWimDa- ta) MESSAGE_HANDLER(MM_WIM_CLOSE, OnWim- Close) MESSAGE_HANDLER(MM WOM_OPEN, OnWomO- pen) MESSAGE_HANDLER(MM WOM_DONE, OnWom- Done) MESSAGE_HANDLER(MM WOM_CLOSE, OnWom- Close) MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLBut- tonDown) END_MSG_MAP () // ISupportsErrorInfo STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid); // IViewObjectEx STDMETHOD(GetViewStatus)(DWORD* pdwStatus) { ATLTRACE(_T(“ IViewObjectExImpl::GetViewStatus\n”)); *pdwstatus = VIEWSTATUS_SOLIDBKGND |VIEWSTATUSOPAQUE; return S_OK; } // IWebEcg public: STDMETHOD(get_ViewECG)(/* [out, retval]*/ BOOL *pVal); STDMETHOD(put_ViewECG)(/*[in]*/ BOOL newVal); STDMETHQD(get_Average_Count)(/* [out, retval]*/ long *pVal); STDMETHOD(put_Average_Count)(/* [in]*/ long newVal); STDMETHOD(get_Age)(/*[out, retval]*/ short *pVal); STDMETHOD(put_Age)(/*[in]*/ short newVal); STDMETHOD(get_AddNewPoint)(/* [out, retval]*/ short *pVal); STDMETHOD(put_AddNewPoint)(/*[in]*/ short newVal); STDMETHOD(get_MicrophoneNotServer)(/*[out, retval]*/ short *pVal); STDMETHOD(put_MicrophoneNotServer)(/*[in]*/ short newVal); STDMETHOD(get_ServerAddress)(/*[out, retval]*/ BSTR *pVal); STDMETHOD(put_ServerAddress)(/*[in]*/ BSTR newVal); STDMETHOD(get_DetectPercent)(/*[out, retval]*/ double *pVal); STDMETHOD(put_DetectPercent)(/*[in]*/ double newVal); STDMETHOD(get_BPMFontSize)(/*[out, retval]*/ short *pVal); STDMETHOD(put_BPMFontSize)(/*[in]*/ short newVal); STDMETHOD(get_Filtering)(/*[out, retval]*/ short *pVal); STDMETHOD(put_Filtering)(/*[in]*/ short newVal); STDMETHOD(get_VolumeThreshold)(/*[out, retval)*/ short *pVal); STDMETHOD(put_VolumeThreshold)(/*[in]*/ short newVal); STDMETHOD(get_DisplaySeconds)(/*[out, retval]*/ short *pVal); STDMETHOD(put_DisplaySeconds)(/*[in]*/ short newVal); STDMETHOD(get_ForeWidth)(/*[out, retval]*/ long *pVal); STDMETHOD(put_ForeWidth)(/*[in]*/ long newVal); STDMETHOD(get_StepmV)(/*[out, retval]*/ double *pVal); STDMETHOD(put_StepmV)(/*[in)*/ double newVal); STDMETNOD(get_MaxmV)(/*[out, retval]*/ double *pVal); STDMETHOD(put_MaxmV)(/*[in]*/ double newVal); STDMETHOD(get_MinmV)(/*[out, retval]*/ double *pVal); STDMETHOD(put_MinmV)(/*[in]*/ double newVal); STDMETHOD(get_GridStyle)(/* [out, retval]*/ long *pVal); STDMETHOD(put_GridStyle)(/* [in]*/ long newVal); STDMETHOD(get_GridColor)(/* [out, retval]*/ OLE_COLOR *pVal); STDMETHOD(put_GridColor)(/*[in]*/ OLE_COLOR newVal); HRESULT FireviewChange(); HRESULT OnDraw(ATL_DRAWINFO& di); HRESULT drawECG( ATL_DRAWINFO& di ) HRESULT drawBPMPoints( ATL.DRAWINFO& di ); HRESULT OnWimOpen(UINT uMsg, WPARAM wParam, LPARAM lparam, BOOL& bHandled); HRESULT OnWimData(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); HRESULT OnwimClose(UINT uMsg, WPARAM wParam, LPARAM lparam, BOOL& bHandled); HRESULT OnWomOpen(UINT uMsg, WPARAM wParam, LPARAM lparam, BOOL& bHandled); HRESULT OnWomDone(UINT uMsg, WPARAM wParam, LPARAM lparam, BOOL& bHandled); HRESULT OnWomClose(UINT uMsg, WPARAM wParam, LPARAM lparam, BOOL& bHandled); LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lparam, BOOL& bHandle d); CComPtr<IPictureDisp> m_pMouseIcon; OLE_COLOR m_clrBackColor; OLE_COLOR m_clrBorderColor; OLE_COLOR m_clrForeColor; OLE_COLOR m_clrGnidColor; BOOL m_bBorderVisible; long m_nBorderStyle; long m_nBorderWidth; long m_nForewidth; long m_nGnidStyle; long m_nMousePointer; short m_nVolumeThreshold; short m_nFiltening; short m_nBPMFontSize; CComBSTR m_bstrFileName; CComBSTR m_bstrServerAddress; bool m_bConnectedToServer; double m_dMinmV; double m_dMaxmV; double m_dStepmV; double m_dDetectPercent; // Shouldn't be public, but I don't care about nice now as // much as getting something worki BOOL m_bMicrophoneNotServer; signed char * m_pCardioGram; unsigned char * m_pVerticalBar; POINT * m_pCardioGramPoints; unsigned long m_dwCardioGramLength; bool m_bRecording; unsigned char * m_pWavBuf[4]; unsigned char * m_pFullVav; unsigned long m_dwFullVavLength; HWAVEIN m_hWaveIn; PWAVEHDR m_pWaveHdr[4]; unsigned short m_nBPM; unsigned long m_dwStartDawing; unsigned long m_dwStopDrawing; HBITMAP m_hbmpWorkBitmap; HDC m_hdcWorkDC; bool m_bFrozen; RECT m_rctFreezeButton; SOCKET m_sckLocalSocket; sockaddr_in m_sckadrRemoteSocketAddress; WSADATA m_wsaWSAData; private: void NewRawData(signed short * RawData, unsigned long NumData); void ConnectToServer(); void drawpoints( HDC &dc, RECT &rc, int dataStart, int xStart, int xEnd, int numBars ); void upArrow( POINT *pPoints, int x, int y, int wid ); void downArrow( POINT *pPoints, int x, int y, int wid ); CBPMDetector mBPM; bool mbDrawECG; short mAge; short mMaxHR; short mBPMSampleCount; ringbuf < int > mBPMData; short mSampsPerBPMPt; short mCumSamps; // Members for drawing point graph int mMaxPoints; // # points which can be drawn int mPointWidth; // point width int mMinPointWidth; // minimum point width int mSpaceWidth; // width of space between points int mCurPoint; // current point # int mPointsRead; // total points read in int mPointsPerCyc; // total points to read before wrap int mDrawingWid; // width of actual drawing area // General drawing vars double mXscale; double mYscale; double mYscale._mV; double mYscale_Cnts; double mXoffset; double mYoffset; int mHeight; int mWidth; int mPrevWidth; int mYmax; long mMaxHrY; long mMinHrY; }; // end of class CWebEcg #endif //_WEBECG_H_(—) // WebEcg.cpp Implementation of CWebEcg #include “stdafx.h” #include “WebEcgControl.h” #include “WebEcg.h” #include “stdio.h” #include “DemodECG.h” #include “QRS.h” //----------------------------------------------------------------------------------------- // Constants //----------------------------------------------------------------------------------------- #define FREEZE_HEIGHT 30 const COLORREF cnYello = RGB( 255, 255, 0 ); const COLORREF cnRed = RGB( 255, 0, 0 ); const COLORREF cnGreen = RGB( 0, 255, 0 ); const COLORREF cnPurp = RGB( 200, 0, 200 ); const COLORREF cnBlue = RGB( 0, 0, 255 ); const COLORREF cnLtBlu = RGB( 50, 100, 255 ); const COLORREF cnBlack = RGB( 0, 0, 0 ); //----------------------------------------------------------------------------------------- // Function Prototypes //----------------------------------------------------------------------------------------- inline void DrawLine(HDC hdc, long x1, long y1, long x2, long y2); void DrawString( HDC dc, HFONT font, UINT align, COLORREF col, int left, int top, char *str ); //----------------------------------------------------------------------------------------- // InterfaceSupportsErrorInfo //----------------------------------------------------------------------------------------- STDMETH0DIMP CWebEcg::InterfaceSupportsErrorlnfo( REFIID riid ) { static const IID* arr[] = { &IID_IWebEcg, }; for (int i=0;i<sizeof(arr)/sizeof(arr[0]);i++) { if (InlineIsEqualGUID(*arr[i],riid)) return S_OK; } return S_FALSE; } HRESULT CWebEcg::FireViewChange() if (m_bInPlaceActive) { // Active if (m_hWndCD != NULL) return (::InValidateRect(m_hwndCD, NULL, FALSE)) ? S_OK : E_FAIL; // Window based if (m_spInPlaceSite !=]NULL) return m_spInPlaceSite->InvalidateRect(NuLL, FALSE); // Windowless } // Inactive SendOnViewChange(DVASPECT_CONTENT); return S_OK; } LRESULT CWebEcg::OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM iParam, BOOL & b Handled) { POINTs ClickPos; ClickPos = MAKEPOINTS(lparam); if ( (ClickPos.y >= m_rctFreezeButton.top) && CClickPos.y <= m_rctFreezeButt on.bottom) && (ClickPos.x >= m_rctFreezeButton.left) && (ClickPos.x <= m_rctFreezeButt on.right) ) { m_bFrozen = !m_bFrozen; // Fire_Click(); FireViewChange(); } return 0; } HRESULT CWebEcg::OnDraw( ATL_DRAWINFO& di ) { if( mbDrawECG ) return drawECG( di ); else return drawBPMPoints( di ); } // end of OnDraw HRESULT CWebEcg::drawEcG( ATL_DRAWINFO& di ) { unsigned long i; double y; long yPos; long xPos long StartXPos[2]; long StopXPos[2]; long LeftEdge; static long NextStartPos=0; bool Wrapped; char buf[80]; HRESULT Result; double WorkMin; double WorkMax; double OverShoot; static SIZE BPMsize; static ATL_DRAWINFO PrevDI; /* // Handy little trick for very simple debugging output. // writes whatever you want directly to the screen... HDC ScreenDC; ScreenDC = ::GetDC(0); sprintf(buf,“%d − %d ”, m_dwstartDrawing, m_(—dwStopDra) wing); SetBkMode(ScreenDC,OPAQUE); TextOut(ScreenDC,0,0,_T(buf),strlen(buf)); ReleaseDC(ScreenDC); */ if ((!m_bRecording) && (m_hWndCD) && (m_bMicro- phoneNotServer)) { m_bRecording = true; WAVEFORMATEX  waveformat; // Set up wave recording. waveformat.wFormatTag = WAVE_FORMAT_PCM; waveformat.nchannels = 1; waveformat.nSamplesPerSec = SAMPLE_FREQ; waveformat.nAvgBytesPerSec = SAMPLE_FREQ; waveformat.nBlockAlign = 1; waveformat.wBitsPerSample = 8; waveformat.cbSize = 0; Result = waveInOpen (&m_hWaveIn, WAV_MAPPER, &waveformat, (DWORD) m_hwndCD, 0, CALLBACK_WINDOW); } if (m_dMaxmV > m_dMinmV) { WorkMax= m_dMaxmV; WorkMin = m_dMinmV; } else if (m_dMaxmV == m_dMinmV) { WorkMax = m_dMaxmV + 0.5; WorkMin = m_dMaxmV − 0.5; } else { WorkMax = m_dMinmV; WorkMin = m_dMaxmV; } Overshoot = (WorkMax − WorkMin)/20.0; RECT& rc = *(RECT*)di.prcBounds; if (m_pcardioGramPoints == NULL) { m_pCardioGramPoints = new(POINT[m_dwCar- dioGramLength]); mPrevWidth = −1; // Force following check to reload x positions } if (mwidth !=mprevwidth) { mHeight = rc.bottom − rc.top + 1; // Make room for Freeze/Unfreeze button mHeight −= FREEZE_HEIGHT; mwidth = rc.right − rc.left + 1 // Multiply original value by these mXscale = (double)mwidth/(double)m_dwCardioGramLength; mYscale_mV = −(mHeight)/(WorkMax − WorkMin + (2*Overshoot)); // +/− 2mV // Set mYscale_Cnts to counts, not mV mYscale_Cnts = mYscale_mV / 40.0; // Add these to the result mXoffset = rc.left; //mYoffset = rc.top + mHeight/2; // Algebraically derived. Don't ask me to explain it... In my logbook 1-29-1999 // (Actually, I have “T + .2*Scale” there, but since scale is negative, we have to // subtract instead of add) mYoffset = (rc.top − (Overshoot*mYscalemV)) − WorkMax * (−(rc.bottom − rc.top + 1)/(workMax − workMin + (2*OverShoot))); // Recalculate X positions of all of the Cardiogram points. for (i=0; i<m_dwCardioGramLength; i++) { m_pCardioGramPoints [i].x = (long) (i*mXscale+mXoffset); } } mPrevWidth = mWidth; HDC WorkDC; HBITMAP WorkBitmap; HBITMAP OrigWorkBitmap; // Hold onto (and re-use) WorkDC and WorkBitmap if we can, but rebuild them // if anything in the DrawInfo changes. if (memcmp((void *)&di, (void *)&PrevDI, sizeof PrevDI) != 0) { DeleteObject(m_hdcWorkDC); DeleteObject(m_hbmpWorkBitmap) m_hdcWorkDC = CreateCompatibleDC(di.hdcDraw); m_hbmpWorkBitmap = CreateCompatibleBit- map(di.hdcDraw mWidth, mHeight); // something weird has happened - redraw the entire screen. m_dwStartDrawing = m_dwStopDrawing + 1 if (m_dwStartDrawing == m_dwCardioGramLength) m_dwStartDrawing = 0; } WorkDC = m_hdcEorkDc; WorkBitmap = m_hbmpWorkBitmap; memcpy((void *)&PrevDI, (void *)&di, sizeof PrevDI); OrigWorkBitmap = (HBITMAP)SelectObject(WorkDC, WorkBitmap); SetBkColor(WorkDC, m_clrBackColor); SetTextColor(workDC, m_clrGridColor); HPEN OrigPen; HBRUSH OrigBrush; LOGBRUSH BrushDesc; BrushDesc.lbStyle = PS_SOLID; BrushDesc.lbCol or = m_clrBackColor; OrigBrush = (HBRUSH)SelectObject(WorkDC, CreateBrushIn- direct(&BrushDesc)); OrigPen = (HPEN)SelectObject(WorkDC, CreatePen(PS_SOL- ID | PS_INSIDEFRAME, 0, BrushDesc.lbColor)); // StopXPos[0] is the most recent point's position if (m.dwStopDrawing > m_dwStartDrawing) { Wrapped = false; StartXPos[0] = NextStartPos StopXPos[0] = (long)(m_dwStopDrawing * mxscale + mxoffset); Rectangle(WorkDC, StartXPos[0]−1, rc.top, StopXPos[0]+1, rc.bottom − FREEZE_HEIGHT); // Grab the last vertical line of the previous section so we can merge w ith it. LeftEdge = (StartXPos [0]?StartXPos [0]−1:0); BitBlt(WorkDC,  LeftEdge, rc.top, 1, mHeight, di.hdcDraw, LeftEdge, rc.top, SRCCOPY); } else { Wrapped = true; StartXPos[0] = rc.left; StopXPos[0] =(long)(m_dwStopDrawing * mxscale + mxoffset); StartXPos[1] = NextStartPos; StopXPos[1] = rc.right Rectangle(WorkDC, StartXPos[0]−5, rc.top, StopXPos[0]+5, rc.bottom − FREEZE_HEIGHT); Rectangle(workDC, StartXPos[1]−5, rc.top, StopXPos[1]+5, rc.bottom − FREEZE_HEIGHT); // Grab the last vertical line of the previous section so we can merge w ith it. LeftEdge = (StartXPos[1]?StartXPos [1]−1:0); BitBlt(WorkDC, LeftEdge, rc.top, 1, mHeight, di.hdcDraw, LeftEdge, rc.top, SRCCOPY); } DeleteObject(SelectObject(WorkDC, OrigBrush)); // Now draw the grid in GridColor double LowAxis; double HighAxis; LowAxis = WorkMin; HighAxis = WorkMax; while ((LowAxis − m_dStepmV) >= (WorkMin − Overshoot)) LowAxis −= m_dStepmV; while ((HighAxis + m_dStepmV) <= (WorkMax + Overshoot)) HighAxis += m_dStepmV; DeleteObject(SelectObject (WorkDC, CreatePen(m_nGridStyle, 0, m_clrGridColor) )) if (wrapped) { // Horizontal lines for (y=LowAxis; y<=HighAxis; y+=m_dStepmV) { yPos = (long)(y*mYscale_mV+myoffset); DrawLine(WorkDC, StartXPos[0], yPos, StopXPos[0]+1, yPos); DrawLine(WorkDc, StartXPos[1], yPos, StopXPos[1]+1, yPos); // Vertical lines for (i=m_dwStartDrawing; i < m_dwCardioGramLength; i++) { if (m_pVerticalBar[i] & 1) { xPos = (long)(i * mxscale + mxoffset); DrawLine(WorkDC, XPos, rc.top, XPos, rc.bottom − FREEZE_HEIGHT); } } // Yes, that should be <=, not <, and the one above should be // <, not <=... for (i=0; i <= m_dwStopDrawing; i++) { if (m_pVerticalBar[i] & 1) { XPos = (long)(i * mxscale + mxoffset); DrawLine(WorkDC, XPos, rc.top, XPos, rc.bottom − FREEZE_HEIGHT); } } // OK, now draw the Cardiogram in ForeColor for (i=(m_dwStartDrawing?m_dwStartDrawing−1:0); i < m_dwCardioGramLength ; i++) { m_pCardioGramPoints[i].y = (long)(m_pCar- dioGram[i] * mYscale_Cnts + mYoffset); m_pCardioGramPoints[i].x = (long)(i * mXscale +mxoffset); } m_pCardioGramPoints[m_dwCardioGramLength−1].x = rc.right; DeleteObject(SelectObject(WorkDC, CreatePen(PS_SOLID, m_nForeWidth, m_cl rForeColor))); Polyline(WorkDC, &(m_pCardioGramPoints[m_dwStart- Drawing]), m_dwCardioGra mLength − m_dwStartDrawing); // OK, now draw the Cardiogram in ForeColor for the wrapped section for (i=0; i <= m_dwStopDrawing; i++) { m_pCardioGramPoints[i].y = (long)(m_pCar- dioGram[i] * mYscale_Cnts + mYoffset); m_pCardioGramPoints[i].x = (long)(i * mXscale +mXoffset); } m_pCardioGramPoints[m_dwStopDrawing) .x = StopXPos[0]; Polyline(WorkDC, m_pCardioGramPoints, m_dwStop- Drawing + 1); } else { // Horizontal lines for (y=LowAxis; y<=HighAxis; y+=m_dStepmV) { yPos = (long)(y*mYscale_mV+mYoffset); DrawLine(WorkDC, StartXPos[0], yPos, StopXPos[0]+1, yPos); } // Vertical lines for (i=m_dwStartDrawing; i <= m_dwStopDrawing; i++) { +L,7 if (m_pVerticalBar[i] & 1) { XPos = (long)(i * mXscale + mXoffset); DrawLine(WorkDC, XPos, rc.top, XPos, rc.bottom − FREEZE_HEIGHT); } } // OK, now draw the Cardiogram in ForeColor for (i=(m_dwStartDrawing?m_dwStartDrawing−1:0); i <= m_dwStopDrawing; i+ { m_pCardioGramPoints[i].y = (long)(m_pCardioGram[i] * mYscale_Cnts + mYoffset); m_pCardioGramPoints[i].x = (long)(i * mXscale + mXoffset); } m_pCardioGramPoints[(m_dwStartDraw- ing?m_dwStartDrawing−1:0))].x = StartXP ,os[0]−1; m_pCardioGramPoints[m_dwStopDrawing) .x = StopXPos[0]; DeleteObject(SelectObject(workDC, CreatePen (PS_SOLID, m_nForeWidth, m_cl rForeColor))); Polyline(workDC, &(m_pCardioGramPoints[(m_dwStart- Drawing?m_dwStartDrawin g−1:0)]), m_dwStopDrawing − (m_dwStartDrawing?m_dwStartDraw- ing−1:0) + 1); // SetPixelV(WorkDC, m_pCardioGram- Points[m_dwStopDrawing].x,m_pCard ioGramPoints[m_dwStopDrawing].y,m_clrForeColor); } // Now draw the grid labels, outlined in BackColor SetTextAlign(WorkDC, TA_LEFT); SetBkMode(WorkDC, TRANSPARENT); SetTextColor(WorkDC, m_clrBackColor); for (y=LowAxis; y<=HighAxis; y+=m_dStepmV) { yPos = (long) (y*mYscale_mV+mYoffset); sprintf(buf, “%5.2fmV”,y); TextOut(WorkDC, rc.left+9, yPos, _T(buf),strlen(buf)); TextOut(workDc, rc.left+11, yPos, _T(buf),strlen(buf)); TextOut(WorkDC, rc.left+10, yPos−1, _T(buf),strlen(buf)); TextOut(WorkDC, rc.left+10, yPos+1, _T(buf),strlen(buf)); } SetBkMode(WorkDC, TRANSPARENT); SetTextColor(WorkDC, m_clrGridColor); for (y=LowAxis; y<=HighAxis; y+=m_dStepmV) { yPos = (long)(y*mYscale_mV+mYoffset); sprintf(buf, “%5.2fmV”,y); TextOut(WorkDC, rc.left+10, yPos, _T(buf),strlen(buf)); } // Copy the newly created region(s) to the screen. if (Wrapped) { BitBlt(di.hdcDraw,rc.left,rc.top,StopXPos[0] − rc.left + 1,mHeight,WorkD C,rc.left, rc.top,SRCCOPY); BitBlt(di.hdcDraw,StartXPos [1],rc.top,rc.right − StartXPos[1] +1,mHeigh t,WorkDC,StartXPos [1],rc.top,SRCCOPY); } else { LeftEdge =(StartXPos[o]?StartXPos[0]−1:0); BitBlt(di.hdcDraw, LeftEdge, rc.top, StopxPos[0] − LeftEdge + 1, mHeight WorkDC,  LeftEdge, rc.top, SRCCOPY); } // Draw BPM in upper right HFONT OrigFont; OrigFont = (HFONT)SelectObject(WorkDC, CreateFont(−MulDiv(m_nBPMFontXize,Get DeviceCaps(WorkDC,LOGPIXELSY),72), 0/*mWidth*/, 0/*escapement*/, 0/*orientation*/, FW_NORMAL/*weight*/, FALSE/*Underline*/, FALSE/*StrikeOut*/, DEFAULT_CHARSET, OUT_TT_ONLY_PRECIS/*Outputprecision*/, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_ROMAN, “Times New Roman” sprintf(buf, “    %d BPM”, m_nAvgBPM); // sprintf(buf, “    %d %d BPM”, m_nBPM, m_nAvgBPM ); SetBkMode(WorkDC, OPAQUE); SetTextAlign(WorkDC, TA_RIGHT); SetTextColor(WorkDC, m_clrBackColor); SetBkColor(WorkDC, m_clrGridColor) TextOut(WorkDC, rc.right, rc.top, _T(buf), strlen(buf)); // Figure out how big the biggest reading we will ever take is. GetTextExtentpoint32(WorkDC, _T(“999 BPM ), strlen(“999 BPM”), &BPMsize); //   GetTextExtentPoint32(WorkDC, _T(“999 999 BPM”), strlen(“999 999 BPM”), & BPMsize); DeleteObject(SelectObject(WorkDC, OrigFont)); SetBkMode (di.hdcDraw, TRANSPARENT); DeleteObject(SelectObject(WorkDC, OrigPen)); BitBlt(di.hdcDraw, rc.right − BPMsize.cx + 1, rc.top, BPMsize.cx, BPMsize.cy workDc,  rc.right − BPMsize.cx + 1, rc.top, SRCCOPY); // Draw the sweeping line OrigPen = (HPEN)SelectObject(di.hdcDraw, CreatePen(PS_SOLID, 2, RGB(255,255, 255))); DrawLine(di.hdcDraw, StopxPos[0]+3, rc.top, StopXPos[0]+3, rc.bottom − FREEZ E_HEIGHT) DeleteObject(SelectObject(di.hdcDraw, OrigPen)); // Do not delete the returned Bitmap, since it is tracked as a member of thi s object. SelectObject(WorkDC, OrigWorkBitmap); // DeleteObject(WorkBitmap); // HACKHACKHACK - Deleted elsewhere // DeleteDC(WorkDC); // HACKHACKHACK - Deleted elsewhere /* // Hack for drawing BPM numbers next to detection point. // Probably needs to be removed for final version. SetTextAlign(di.hdcDraw, TA_RIGHT); SetBkMode(di.hdcDraw, TRANSPARENT); for (i=m_dwStartDrawing; i <= m_dwStopDrawing; i++) { if (m_pVerticalBar[i] & 254) { XPos = (long)(i * mXscale + mXoffset); YPos = (long)(m_pCardioGram[i] * mYscale_Cnts + mYoffset); sprintf(buf, “%d BPM”, (m_pVerticalBar[i]>>1) & 0x7F); SetTextColor(di.hdcDraw, m_clrBackColor); TextOut(di.hdcDraw, xPos+1, yPos, _T(buf),strlen(buf)) ; TextOut(di.hdcDraw, xPos−1, yPos, _T(buf),strlen(buf)); TextOut(di.hdcDraw, xPos, yPos+1, _T(buf),strlen(buf)); Textout (di.hdcDraw, xPos, yPos−1, _T(buf),strlen(buf)); SetTextColor(di.hdcDraw, m_clrGridColor); TextOut(di.hdcDraw, xPos, yPos, _T(buf),strlen(buf)); } } */ if (m_bFrozen) strcpy(bufl “Unfreeze Display”); else strcpy(buf, “Freeze Display”); BrushDesc.lbStyle = PS_SOLID; BrushDesc.lbCol or = m_clrBackColor; OrigBrush = (HBRUSH)SelectObject(di.hdcDraw, CreateBrushIn- direct(&BrushDesc) ); OrigPen = (HPEN)SelectObject(di.hdcDraw, CreatePen(PS_SOL- ID | PS_INSIDEFRAME , 2, m_clrGridColor)); m_rctFreezeButton.left = rc.left; m_rctFreezeButton.right = rc.right; m_rctFreezeButton.top = rc.bottom − FREEZE_HEIGHT; m_rctFreezeButton.bottom = rc.bottom; Rectangle(di. hdcDraw, m_rctFreezeButton. left, m_rctFreezeButton.top, m_rctFreezeButton.right, m_rctFreezeButton.bottom); SetTextColor(di.hdcDraw, m_clrGridColor); DrawText(di.hdcDraw, _T(buf), strlen(buf), &m_rctFreezeButton, DT_CENTER | D T_VCENTER | DT_SINGLELINE); DeleteObject(SelectObject(di.hdcDraw, OrigPen)); DeleteObject(SelectObject(di.hdcDraw, OrigBrush)); m_dwStartDrawing = m_dwStopDrawing+1; if (m_dwStartDrawing >= m_dwCardioGramLength) { m_dwstartDrawing = 0; NextStartPos = rc.left; } else { NextStartPos = StopXPos[0] +1; } return S_OK; // end of drawECG HRESULT CWebEcg::drawBPMPoints( ATL_DRAWINFO &di ) { char buf[80]; HRESULT Result; static SIZE BPMsize; static ATL_DRAWINFO PrevDI; static long maxTextWidth = 0; //------------------------------------------------------------ // Turn on the microphone input if necess //------------------------------------------------------------ if( (!m_bRecording) && (m_hWndCD) && (m_bMicro- phoneNotserver) ) { m_bRecording = true; WAVE FORMATEX waveformat; // Set up wave recording. waveformat.wFormatTag = WAVE_FORMAT_PCM; waveformat.nchannels = 1; waveformat.nSamplesperSec = SAMPLE_FREQ; waveformat.nAvgBytesPerSec = SAMPLE_FREQ; waveformat.nBlockAlign = 1; waveformat.wBitsPerSample = 8; waveformat.cbSize 0; Result = waveInOpen( &m_hWaveIn, WAVE_MAPPER, &waveformat, (DWORD) m_hWndCD, 0, CALL- BACK_WINDOW); // end if to turn on wave recorder //------------------------------------------------------------ // If no data yet, return //------------------------------------------------------------ if( mBPMData.empty() ) return S_OK; RECT & rc = *(RECT*)di.prcBounds; //------------------------------------------------------------ // Recalculate dimensions, offsets if // width changes //------------------------------------------------------------ bool  widthchanged = false; if( mwidth != mPrevWidth ) { widthChanged = true; mHeight = rc.bottom − rc.top; mWidth = rc.right − rc.left; mXoffset = rc.left + 66.0; mYoffset = 0.0; mDrawingWid = mWidth − mXoffset; mPointWidth = mDrawingWid / (mBPMSample- Count + (mBPMSample- Count − 1) * mSpaceWidth); mPointWidth = _max( mMinPointWidth, mPointWidth ); mMaxPoints = mDrawingWid / (mPointWidth + mSpaceWidth); mPointsPerCyc =mBPMSampleCount * mMaxPoints; if( mPointsRead >= mPointsPerCyc ) mPointsRead = 0; // scale original values with these mXscale = (double) (mPointWidth + mSpaceWidth); mYscale = (double) (mHeight − mYoffset) / (double) mMaxHR; mYmax = rc.bottom; mMaxHrY = (long) ( ((double) (mMaxHR − mMaxHR * .85) * mYscale) + mYoffset ); mMinHrY = (long) ( ((double) (mMaxHR − mMaxHR * .65) * myscale) + mYoffset ); } // end if mwidths don't match mPrevWidth = mWidth; HBITMAP workBitmap; HBITMAP origWorkBitmap; HFONT origFont; HFONT lSmlFont; HFONT lMedFont; HFONT lBigFont; HDC workDC; HPEN origPen; HBRUSH origBrush; LOGBRUSH brushDesc; brushDesc.lbStyle = PS_SOLID; brushDesc.lbColor = m_clrBackColor; //------------------------------------------------------------ // Hold onto (and re-use) workDC and // WorkBitmap if we can, but rebuild // them if anything in the DrawInfo // changes. //------------------------------------------------------------ bool  dcChanged = false; if( memcmp( (void *) &di, (void *) &PrevDI, sizeof PrevDI ) != 0 ) { DeleteObject( m_hdcWorkDC ); DeleteObject( m_hbmpWorkBitmap ); m_hdcWorkDC = CreateCompatibleDC( di.hdcDraw ); m_hbmpWorkBitmap =CreateCompatibleBitmap( di.hdcDraw, mWidth, mHeight ) dcChanged = true; } // end if prey DC not = this workDC = m_hdcWorkDC; workBitmap = m_hbmpWorkBitmap; memcpy( (void *)&prevDI, (void *)&di, sizeof PrevDI ); origBrush = (HBRUSH) SelectObject( workDC, CreateBrushIn- direct( &brushDesc ) origPen = (HPEN) SelectObject( workDC, Create- Pen(PS_SOLID | PS_INSIDEFRAME 0, brushDesc.lbColor) ); origworkBitmap = (HBITMAP) SelectObject( workDC, workBitmap ); SetBkColor( workDC, m_clrBackColor ); //------------------------------------------------------------ // Initialize positions, numpoints //------------------------------------------------------------ int dataStart = 0; int numPoints = mBPMData.numPoints(); //------------------------------------------------------------ // If there are no new data points, some // other event caused OnDraw to be called. // Redraw the entire screen. Also redraw // if width changed. //------------------------------------------------------------ if( !numPoints || widthChanged || dcChanged ) { if( mBPMData.wrapped() || mPointsRead >= mMaxPoints ) { dataStart = mBPMData.backBy( mMaxPoints ); numPoints = mMaxPoints; } else { datastart = 0; numPoints = mBPMData.curPos(); } else { datastart = mBPMData.lastRead(); numPoints = _min( mMaxPoints, numPoints ); } //------------------------------------------------------------ // Determine where to start drawing on the // screen, and if wrapping occurs //------------------------------------------------------------ bool drawingWrapped = false; int screenStart; // numPoints will normally be < mPointsRead if( numPoints < mPointsRead ) screenStart = (mPointsRead − numPoints) % mMaxPoints; else { //------------------------------------------------------------ // in this case, mPointsRead has wrapped // around from mPointsPerCyc back to 0, // which requires adding mPointsPerCyc // back in until numPoints < mPointsRead //------------------------------------------------------------ screenstart = (mPointsPerCyc + (mPointsRead − numPoints)) % mMaxPoints; } // end else numPoints > mPointsRead //------------------------------------------------------------ // Determine if drawing will be wrapped //------------------------------------------------------------ if( screenStart + numPoints >= mMaxPoints ) drawingWrapped = true; //------------------------------------------------------------ // if not wrapped, just draw straight set // of points. //------------------------------------------------------------ if( !drawingWrapped ) { int xStart = mXoffset + (int)(screenStart * mXscale); int xEnd = xStart + numPoints * mXscale; drawPoints( workDc, rc, datastart, xStart, xEnd, numPoints ); //------------------------------------------------------------ // Blit the new area to the screen //------------------------------------------------------------ int xWidth =numPoints * mXscale; BitBlt( di.hdcDraw, xStart, rc.top, xWidth, mHeight, workDC, xStart, rc.top, SRCCOPY ); } // end if not wrapped else { //------------------------------------------------------------ // Calculate bounding rectangles of both // regions //------------------------------------------------------------ int xStart[2]; int xEnd[2] int dataPoints [2]; dataPoints[0] = mMaxPoints − screenStart dataPoints[1] = numPoints − dataPoints[0]; xStart[0] = mXoffset + (int) (screenStart * mXscale); xEnd[0] = xStart[0] + dataPoints[0] * mXscale; xStart[1] = mXoffset; xEnd[1] = xStart[1] + dataPoints[1] * mXscale; int datStart2 = dataStart; mBPMData.incr( datStart2, dataPoints [0] ); //------------------------------------------------------------ // Draw the points //------------------------------------------------------------ drawPoints( workDC, rc, dataStart,  xStart[0], xEnd[0], dataPoints[0] ); drawpoints( workDC, rc, dataStart2, xStart[1], xEnd[1], dataPoints[1] ); //------------------------------------------------------------ // But the new areas to the screen //------------------------------------------------------------ int xWidth[2]; xWidth[0] = dataPoints[0] * mXscale; xWidth[1] = dataPoints[1] * mXscale; BitBlt( di.hdcDraw, xStart[0], rc.top, xWidth[0], mHeight, workDC, xStart[0], rc.top, SRCCOPY ); BitBlt( di.hdcDraw, xStart[1], rc.top, xWidth[1], mHeight, WorkDC, xStart[1], rc.top, SRCCOPY ); } // end else wrapped //------------------------------------------------------------ // Setup for drawing text //------------------------------------------------------------ lSmlFont = CreateFont( −MulDiv( 9, GetDeviceCaps( workDC, LOGPIXELSY ), 72 ), 0 /*mWidth*/, 0 /*escapement*/, 0 /*orientation*/, FW_NORMAL/*weight*/, FALSE /*Italic*/ FALSE /*Underline*/, FALSE /*StrikeOut*/, DEFAULT_CHARSET, OUT_TT_ONLY_PRECIS /*Out- putPrecision */, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_ROMAN, “Times New Roman” ); lMedFont = CreateFont( −MulDiv( 10, GetDeviceCaps( workDC, LOGPIXELSY ), 72 ), 0 /*mWidth*/, 0 /*escapement*/, 0 /*orientation*/, FW_NORMAL/*weight*/, FALSE /*Italic*/ FALSE /*Underline*/, FALSE /*StrikeOut*/, DEFAULT_CHARSET, OUT_TT_ONLY_PRECIS /*Out- putPrecision */, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_ROMAN, “Times New Roman” “Arial” ); lBigFont = CreateFont) −MulDiv( 10, GetDeviceCaps( workDC, LOGPIXELSY ), 72 ),+TZ, 0 /*mWidth*/, 0 /*escapement*/, 0 /*orientation*/, FW_NORMAL/*weight*/, FALSE /*Italic*/ FALSE /*Underline*/, FALSE /*StrikeOut*/, DEFAULT_CHARSET, OUT_TT_ONLY_PRECIS /*Out- putPrecision */, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_ROMAN, “Times New Roman” “Arial” ); origFont = (HFONT) SelectObject( workDC, lBigFont ); SetTextAlign( workDC, TA_RIGHT ); //------------------------------------------------------------ // Figure out how big the biggest // reading we will ever take is. //------------------------------------------------------------ GetTextExtentPoint32( workDC, _T(“999 BPM”), strlen(“999 BPM”), &BPMsize ); maxTextWidth = _max( BPMsize.cy, maxTextWidth ); SetBkMode( workDC, OPAQUE ); //------------------------------------------------------------ // Max HR at top left //------------------------------------------------------------ DrawString( workDC, lSmlFont, TA_LEFT, cnRed, rc.left+2, rc.top, “MAX” ); sprintf( buf, “%d”, mMaxHR ); DrawString( workDC, lMedFont, TA_RIGHT, cnRed, mCoffset−2, rc.top, buf ); //------------------------------------------------------------ // 85 % of max //------------------------------------------------------------ long hr = (long) ((double) mMaxHR * 0.85); DrawString( workDC, lSmlFont, TA_LEFT, cnLtBlu, rc.left+2, mMaxHrY, “85%” ); sprintf( buf, “%d”, hr ); DrawString( workDC, lMedFont, T_RIGHT, cnLtBlu, mXoffset−2, mMaxHrY, buf ); //------------------------------------------------------------ // 65 % of max //------------------------------------------------------------ hr = (long) ((double) mMaxHR * 0.65); DrawString( workDC, iSmlFont, TA_LEFT, cnLtBlu, rc.left+2, mMinHrY, “65%” ); sprintf( buf, ”%d”, hr ); DrawString( workDC, lMedFont, TA_RIGHT, cnLtBlu, mXoffset−2, mMinHrY, buf ); //------------------------------------------------------------ // Current BMP lower left //------------------------------------------------------------ if( m_nAvgBPM < 10 ) sprintf( buf, “ %d BPM”, m_nAVgBPM ); else if( m_nAvgBPM < 100 ) sprintf( buf, “ %d BPM”, m_nAVgBPM ); else sprintf( buf, “ %d BPM”, m_nAVgBPM ); SetBkColor( workDC, m_clrGridColor ); DrawString( workDC, lBigFont, TA_RIGHT, m_clrBackColor, mXoffset − 2, rc.bottom − BPMsize.cy, buf ); SetBkMode( di.hdcDraw, TRANSPARENT ); // Left border line DeleteObject( SelectObject( workDC, CreatePen( m_nGridStyle, 1, cnBlue ) ) ) DrawLine( workDC, mXoffset−1, rc.top, mXoffset−1, rc.bottom ); SelectObject( workDC, origFont ); SelectObject( workDC, origPen ); SelectObject( workDC, origBrush ); DeleteObject( lSmlFont ); DeleteObject( lMedFont ); DeleteObject( lBigFont ); //------------------------------------------------------------ // Blit new area to screen //------------------------------------------------------------ BitBlt( di.hdcDraw, rc.left, rc.top, mCoffset, rc.bottom, workDC, rc.left, rc.top, SRCCOPY ); return S_OK; } // end of drawBPMPoints void CwebEcg::drawPoints( HDC &dc, RECT &rc, int datastart, int xStart, int xEnd, int numPoints ) { HPEN blackPen = CreatePen( PS_SOLID | PS_IN- SIDEFRAME, 0, cnBlack ); HPEN redPen = CreatePen( PS_SOLID | PS_IN- SIDEFRAME, 0, cnRed ); HPEN gridPen = CreatePen( PS_SOLID | PS_IN- SIDEFRAME, 0, CnLtBlu ); HPEN greenPen = CreatePen( PS_SOLID | PS_IN- SIDEFRAME, 0, cnGreen ); HPEN purpPen = CreatePen( PS_SOLID | PS_IN- SIDEFRAME, 0, cnPurp ); HPEN origPen; HBRUSH bkBrush = CreateSolidBrush( m_clrBackColor ); HBRUSH greenBrush = CreateSolidBrush( cnGreen ); HBRUSH redBrush = CreateSolidBrush( cnRed ); HBRUSH purpBrush = CreateSolidBrush( cnpurp ); HBRUSH origBrush; POINT points[3]; origBrush = (HBRUSH) SelectObject( dc, bkBrush ); origPen = (HPEN) SelectObject( dc, blackPen ); //------------------------------------------------------------ // First clear previous areas //------------------------------------------------------------ Rectangle( dc, xStart, rc.top, xEnd, rc.bottom ); //------------------------------------------------------------ // Draw gridlines //------------------------------------------------------------ SelectObject( dc, gridPen ); int lineStart = xStart − mSpaceWidth; linestart = max( mXoffset, linestart ); DrawLine( dc, lineStart, mMaxHrY, xEnd, mMaxHrY ); DrawLine( dc, lineStart, mM,nHrY, xEnd, mMinHrY ); //------------------------------------------------------------ // Determine where to stop reading in the // ring buffer //------------------------------------------------------------ int   dataStop = dataStart; mBPMData.incr( dataStop, numPoints ); //------------------------------------------------------------ // Draw new points //------------------------------------------------------------ int   x; int   y; for( int i = datastart, xI = 0; i != dataStop; mBPMData.incr( i ), xI++ ) { x = xStart + (int) (xI * mXscale); y = mYmax − (int) (mBPMData[i] * mYscale + mYoffset); if( y < 0 || y >= mYmax ) continue; //------------------------------------------------------------ // Since the upper left x,y is 0,0 // a value > min HR Y means the HR // is too low //------------------------------------------------------------ if( y > mMinHrY ) { SelectObject( dc, purpBrush ); SelectObject( dc, purpPen ) upArrow( points, x, y, mPointWidth ); Polygon( dc, points, 3 ); } else if( y < mMaxHry ) // HR too high { SelectObject( dc, redBrush ); SelectObject( dc, redPen ) downArrow( points, x, y, mPointWidth ); Polygon( dc, points, 3 ); } else // HR just right { SelectObject( dc, greenBrush ); SelectObject( dc, greenPen ); Rectangle( dc, x, y, x + mPointwidth, y + mPointWidth ); } } // end for thru bars //------------------------------------------------------------ // Update ring buffer index to indicate // we've read numPoints //------------------------------------------------------------ mBPMData.updateBy( numPoints ); //------------------------------------------------------------ // Clean up //------------------------------------------------------------ SelectObject( dc, origBrush ); SelectObject( dc, origPen ); DeleteObject( bkBrush ); DeleteObject( redBrush ); DeleteObject( greenBrush ); DeleteObject( purpBrush ); DeleteObject( gridPen ); DeleteObject( blackPen ); DeleteObject( redPen ); DeleteObject( greenPen ); DeleteObject( purpPen ); } // end of drawPoints void CwebEcg::upArrow( POINT *pPoints, int x, int y, int wid ) { pPoints[0] .x = x; pPoints[0] .y = y + wid; pPoints[1] .x = x + wid / 2; pPoints[1] .y = y; pPoints[2] .x = x + wid; pPoints[2] .y = y + wid; } // end of upArrow void CwebEcg::downArrow( POINT *pPoints, int x, int y, int wid ) { pPoints[0] .x = x; pPoints[0] .y = y + wid; pPoints[1] .x = x + wid / 2; pPoints[1] .y = y; pPoints[2] .x = x + wid; pPoints[2] .y = y + wid; } // end of downArrow HRESULT CwebEcg::OnWimOpen(UINT uMsg, WPARAM wParam, LPARAM lparam, BOOL& bHandl ed) { int i; // Should have LOTS of memory checking error handling all through here... m_pFullWav = NULL; for (i=0; i<4; i++) { m_pWavBuf[i] = new(unsigned char[WORK_BUFF- ER_SIZE]); m_pWaveHdr[i] −>lpData = (char *)    m_pwavBuf[i]; m_pWaveHdr[i] −>dwBufferLength = WORK_BUFF-    ER_SIZE; m_pWaveHdr[i] −>dwBytesRecorded = 0; m_pWaveHdr[i] −>dwUser = 0; m_pWaveHdr[i] −>dwFlags = 0; m_pWaveHdr[i] −>dwLoops = 1; m_pWaveHdr[i] −>lpNext = NULL; m_pWaveHdr[i] −>reserved = 0; waveInPrepareHeader (m_hWaveIn, m_pWaveHdr[i], sizeof (WAVEHDR)); waveInAddBuffer (m_hWaveIn, m_pWaveHdr[i], sizeof (WAVEHDR)); } // Start sampling m_dwFullwavLength = 0; MMRESULT mmResult = waveInStart(m_hwaveIn); return S_K; } HRESULT CWebEcg::OnWimData(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandl ed) { const unsigned int OverlapPts = 4; const unsigned int DiscardPts = 4; static int SampleCount; static signed char wavedata[WORK_BUFFER_SIZE+Over- lapPts*DECIMATION]; static short lPDataOut[WORK_BUFFER_SIZE/DECI- MATION + OverlapPts); int mDataOutSize; unsigned long i; bool bSomethingThere; bool initPass = true; bSomethingThere = false; if (!m_bFrozen) { for (i=0; 1<((PWAVEHDR) lParam)−>dwBytesRecorded; i++) { if ( ((unsigned char)(((PWAVEHDR) lParam)−>lpDa- ta[i]) <= 128‘m_nVolu meThreshold) || ((unsigned char)(((PWAVEHDR) lParam)−>lpDa- ta[i]) >= 128+m_nVolum eThreshold) { bSomethingThere = true; break; } } } if (bSomethingThere) { if( initPass ) { // Do first time initialization here initPass = false; // Only do this the first time we get something past the volume thre shold. // Setting m_dwStopDrawing to −1 will work properly as long as the f ollowing // code moves m_dwStopDrawing forward by filling in the new data. I f you do // not have any new data and let OnDraw try to work with m_dwStopDra Wing==−1, // you get a crash. // Also, −1 is an odd value to push into an UNSIGNED long. Fortunat ely, it // has a value of all 1's, and adding one to that will roll it over to all 0's, // so it has the essential −1'ness that we need. m_dwStopDrawing = −1; } for (i=0; i<WORKBUFFER_SIZE; i++) { signed char p = (signed char) ((PWAVEHDR) lParam)−>lpData[i]; signed char q = (signed char) (p − 128); signed char r; wavedata[i+OverlapPts*DECIMATION] = (signed char) (((PWAVEHDR) lParam )−>lpData[i] − 128); r = wavedata[i+OverlapPts*DECIMATION, } DemodECG(lpDataOut, &i DataOutSize, (char *)wavedata, WORK_BUFFER_SIZE + OverlapPts*DECIMATION, 80, 1520); // we are done with this buffer. Return it to the system as soon as // possible. waveInAddBuffer (m_hWaveIn, (PWAVEHDR) lparam, sizeof (WAVEHDR)); // First several ECG points are garbage. // Save the last several ECG points worth of samples to prepend to the // next buffer. That way the first several ECG points of this new buffe r // are garbage and discarded (OK, since they are the last several return ed // this time). if (OverlapPts) { // OK, some explanation here... // we want to move the LAST OverlapPts worth of samples to the front // of wavedata. The end of wavedata is at: // (remember - each ECG point is equivalent to DECIMATION samples) // // wavedata+WORK_BUFFER_SIZE+Over- lapPts*DECIMATION // // (actually, that is the address of the first byte after the end of // wavedata, but if you wanted to see the last 1 byte of wavedata, y ou // would go to that address−1, and if you wanted to see the last 10 bytes // you would go to that address−10, etc. This makes sense when you read // the following) // But we want to start OverlapPts worth of samples before that, so: // // wavedata+WORK_BUFFER_SIZE+Over- lapPts*DECIMATION−OverlapPts*DECIMA TION // // which simplifies to: // // wavedata+WORK_BUFFER_SIZE MoveMemory(wavedata, waveda- ta+WORK_BUFFER_SIZE, Over- lapPts*DECIMATIO N); } // First several returned points are garbage... if (DiscardPts) { iDataOutSize −= DiscardPts MoveMemory(lpDataOut ,&(lpDataOut [DiscardPts)), iDataOutSize * sizeof( lpDataOut[0])); } if (m_bConnectedToServer) { send(m_sckLocalSocket, (const char *)lpDataOut, iDataOutSize*2, 0); } NewRawData(lpDataOut, iDataOutSize); } else { waveInAddBuffer (m_hwaveIn, (PWAVEHDR) lparam, sizeof (WAVEHDR)); } return S_OK; } HRESULT CwebEcg::OnWimClose(uINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHand led) { // Never gets called in time if WaveInClose is called from ˜CWebEcg... //MessageBox(“OnWimClose”,“NOTE”,MB_OK); // waveInUnprepareHeader(m_hWaveIn, m_pWaveHdr1, sizeof(WAVEHDR)); // waveInUnprepareHeader(m_hWaveIn, m_pWaveHdr2, sizeof(WAVEHDR)); // delete m_pWaveHdr1; // delete m_pWaveHdr2; // delete m_pWavBuf[0]; // delete m_pWavBuf[1]; return S_OK; } HRESULT CWebEcg::OnWomOpen(uINT uMsg, WPARAM wParam, LPARAM iParam, BOOL& bHandl ed) { return S_OK; 56 HRESULT CWebEcg::OnWomDone(UINT uMsg, WPARAM wParam, LPARAM iParam, BOOL& bHandl ed) { return S_OK; } HRESULT CWebEcg::OnWomClose(UINT uMsg, WPARAM wParam, LPARAM iParam, BOOL& bHand led) { return S_OK; } STDMETHODIMP CWebEcg::get_GridColor(OLE_COLOR * pVal) { // TODO: Add your implementation code here *pVal = m_clrGridColor; return S_OK; } STDMETHODIMP CWebEcg::put_GridColor(OLE_COLOR newVal) { // TODO: Add your implementation code here if (FireOnRequestEdit(dispidGridColor) == S_FALSE) { return S_FALSE; } m_clrGridColor = newVal; SetDirty(TRUE); FireOnChanged(dispidGridColor); FireViewChange(); return S_OK; } STDMETHODIMP CWebEcg::get_GridStyle(long * pVal) { // TODO: Add your implementation code here *pVal = m_nGridStyle; return S_OK; } STDMETHODIMP CWebEcg::put_GridStyle (long newVal) { // TODO: Add your implementation code here if (FireOnRequestEdit(dispidGridStyle) == S_FALSE) { return S_FALSE; } m_nGridStyle = newVal; SetDirty(TRUE); FireOnChanged(dispidGridStyle); FireViewChange(); return S_OK; } STDMETHODIMP CWebEcg::get_MinmV(doubie * pVal) { // TODO: Add your implementation code here *pVal = m_dMinmV; return S_OK; } STDMEThODINIP CwebEcg::put_MinmV(double newVal) { if (FireOnRequestEdit(dispidMinmV) == S_FALSE) { return S_FALSE; } m_dMinmV = newVal; SetDirty(TRUE) FireOnChanged(dispidMinmV); FireViewChange(); return S_OK; STDMETHODIMP CWebEcg::get_MaxmV(double * pVal) { // TODO: Add your implementation code here *pVal = m_dMaxmV; return S_OK; } STDMETHODIMP CWebEcg::put_MaxmV(double newVal) { if (FireOnRequestEdit(dispidMaxmV) == S_FALSE) { return S_FALSE; } m_dMaxmV = newVal; SetDirty(TRUE); FireOnChanged(dispidMaxmV); FireViewChange(); return S_OK; } STDMETHODIMP CWebEcg::get_stepmV(double * pVal) { // TODD: Add your implementation code here *pVal = m_dStepmV; return S_OK; } STDMETHODIMP CWebEcg::put_StepmV(double newVal) { if (FireOnRequestEdit(dispidStepmV) == S_FALSE) { return S_FALSF } m_dStepmV = newVal; SetDirty(TRUE); FireOnChanged(dispidStepmV); FireViewChange(); return S_OK; } STDMETHODIMP CWebEcg::get_ForeWidth(long * pVal) { // TODO: Add your implementation code here *pVal = m_nForeWidth; return S_OK; } STDMETHODIMP CWebEcg::put_ForeWidth(long newVal) { if (FireOnRequestEdit(dispidForeWidth) == S_FALSE) { return S_FALSE; } m_nForeWidth = newVal; SetDirty(TRUE); FireOnChanged(dispidForeWidth); FireViewChange(); return S_OK; STDMETHODIMP CWebEcg::get_DisplaySeconds(short * pVal) { // TODO: Add your implementation code here *pVal = (short)(m_dwCardioGramLength/ECG_FREQ); return S_OK; } STDMETHODIMP CWebEcg::put_DisplaySeconds(short newVal) // TODO: Add your implementation code here if (FireOnRequestEdit(dispidDisplaySeconds) == S_FALSE) { return S_FALSE; } delete m_pCardioGram; delete m_pCardioGramPoints; delete m_pVerticalBar; m_pCardioGramPoints = NULL; m_dwStartDrawing = 0; m_dwCardioGramLength = ECG_FREQ*newVal; m_pCardioGram = new (signed char[m_dwCardioGramLength]); m_pVerticalBar = new (unsigned char[m_dwCar- dioGramLength]); for (unsigned long i = 0; 1<m_dwCardioGramLength; i++) { m_pCardioGram[i] = 0; m_pVerticalBar[i] = 0; } mBPMSampleCount = newVal * 4; // 4 samples per sec mPointsPerCyc = mBPMSampleCount * mMaxPoints; if( !mPointsPerCyc ) mPointsPerCyc = mBPMSampleCount; mBPMData.resize( mBPMSampleCount ); SetDirty(TRUE); FireOnChanged(dispidDisplaySeconds); FireviewChange(); return S_OK; } // end of put_DisplaySeconds STDMETHODIMP CWebEcg::get_VolumeThreshold(short * pVal) { // TODO: Add your implementation code here *pVal = m_nVolumeThreshold, return S_OK; } STDMETHODIMP CWebEcg::put_VolumeThreshold(short newVal) // TODO: Add your implementation code here if (FireOnRequestEdit(dispidVolumeThreshold) == S_FALSE) { return S_FALSE; } m_nVolumeThreshold = newVal; SetDirty(TRUE); FireOnChanged(dispidVolumeThreshold); FireViewChange(); return S_OK; } STDMETHODIMP CWebEcg::get_Filtering(short * pVal) { // TODD: Add your implementation code here *pVal = m_nFiltering; return S_OK; } STDMETHODIMP CWebEcg::put_Filtering(short newVal) { // TODO: Add your implementation code here if (FireOnRequestEdit(dispidFiltering) == S_FALSE) { return S_FALSE; } m_nFiltering = newVal; SetDirty(TRUE); FireOnChanged(dispidFiltering); FireViewChange(); return S_OK; } STDMETHODIMP CWebEcg::get_BPMFontSize(short * pVal) // TODO: Add your implementation code here *pVal = m_nBPMFontSize; return S_OK; } STDMETHODIMP CWebEcg::put_BPMFontSize(short newVal) { // TODO: Add your implementation code here if (FireOnRequestEdit (dispidBPMFontsize) = S_FALSE) { return S_FALSE; } m_nBPMFontSize = newVal; SetDirty(TRUE); FireOnChanged(dispidBPMFontSize); FireViewChange(); return S_OK; } STDMETHODIMP CWebEcg::get_DetectPercent(double * pVal) { // TODO: Add your implementation code here *pVal = mdDetectPercent; return S_OK; } STDMETHODIMP CWebEcg::put_DetectPercent(double newVal) // TODO: Add your implementation code here if (FireOnRequestEdit(dispidDetectPercent) == S_FALSE) { return S_FALSE; } m_dDetectPercent = newVal; mBPM.DetectPercent( newVal ); SetDirty(TRUE); FireOnChanged(dispidDetectPercent); FireviewChange(); return S_OK; } STDMETHODIMP CWebEcg::get_ServerAddress(BSTR * pVal) { // TODO: Add your implementation code here *pVal = m_bstrServerAddressCopy(); return S_OK; } STDMETHODIMP CWebEcg::put_ServerAddress( BSTR newVal ) { // TODO: Add your implementation code here USES_CONVERSION; // Seems necessary for a call to w2A later to work... if( FireOnRequestEdit(dispidServerAddress) == S_FALSE ) { return S_FALSE; } m_bstrServerAddress = newVal; if( m_bConnectedToServer ) { shutdown( m_sckLocalSocket, 1 ); closesocket( m_sckLocalSocket ); m_bConnectedToServer = false; } if( m_bstrServerAddress.Length() < 7 ) return S_OK; if( m_bMicrophoneNotServer ) ConnectToServer(); SetDirty( TRUE ); FireOnChanged( dispidServerAddress ); FireViewChange(); return S_OK; } STDMETHODIMP CWebECg::get_MicrophoneNotServer(short * pVal) // TODO: Add your implementation code here *pVal = m_bMicrophoneNotServer; return S_OK; } STDMETHODIMP CWebECg::put_MicrophoneNotServer(short newVal) // TODO: Add your implementation code here if (FireOnRequestEdit(dispidMicrophoneNotServer) == S_FALSE) { return S_FALSE; } if (m_bMicrophoneNotServer && newVal) { // Both true -- no change -- don't care { else if ( ! (m_bMicrophoneNotServer || newVal)) { // Neither true -- no change -- don't care } else { // State changed - adjust as necessary. if (m_bMicrophoneNotServer) { // was running from microphone, now from net // must shut down microphone int i waveInReset(m_hWaveIn); waveInClose(m_hWaveIn); // Sleep((1.0/SAMPLE_FREQ)*(2*WORK_BUFF- ER_SIZE)*1000); // Give system time to shut down whatever. // sleep((1000/SAMPLE_FREQ)*(2*WORK_BUFF- ER_SIZE)); // Give syst em time to shut down whatever. // Sleep((1000*2*WORK_BUFFER_SIZE)/SAM- PLE..FREQ); // Give syst em time to shut down whatever. sleep((2000*WORK_BUFFER_SIZE)/SAM- PLE_FREQ); // Give system time to s hut down whatever. for (i= ; i<4; i++) { waveInUnprepareHeader(m_hWaveIn, m_pWaveHdr [i], Sizeof(WAVEHDR)) delete m_pWaveHdr [i]; delete m_pWavBuf [i]; } } else { // was running from net, now from microphone if( m_bstrServerAddress.Length() >= 7 ) ConnectToServer(); m_bRecording = false; } m_bMicrophoneNotServer = newVal; SetDirty(TRUE); FireOnChanged(dispidMiCrophoneNotServer); FireViewChange() } return S_OK; } void CWebEcg::NewRawData( signed short * RawData, unsigned long NumData ) { static int SampleCount; unsigned long i; short CurrFilteredPoint; static unsigned long SamplesSinceQRS; static bool InitPass = true; static short Last2SecsFiltered[ECG.FREQ*2]; static short PrevBPM; // m_dwStopDrawing should always point to the // most recently inserted CardioGram Point. signed char WorkReading; for( i= 0; i < (unsigned long)NumData; i++ ) { // CharFilter is #defined macro in QRS.h that converts input into // SIGNED character, and pegs value at 127 or −128 boundaries. WorkReading = CharFilter( RawData[i] ); // The following set of filters is in the QRS files, and is // taken from a routine in a book on biomedical signal processing. // As we played with the signal, certain parts were changed and/or // removed. CurrFilteredPoint = LowPassFilter( WorkReading ); if( m_nFiltering == 1 ) WorkReading = CharFilter( CurrFilteredPoint ); CurrFilteredPoint = HighPassFilter( CurrFilteredPoint ); if( m_nFiltering == 2 ) WorkReading = CharFilter( CurrFilteredpoint ); workReading += 55; CurrFilteredPoint = Derivative( CurrFilteredpoint ); if( m_nFiltering == 3 ) WorkReading = CharFilter( CurrFilteredPoint ); CurrFilteredPoint = Square( CurrFilteredPoint ); if( m_nFiltering = 4 ) WorkReading = CharFilter( CurrFilteredPoint ); // CurrFilteredPoint = MovingWindowIntegral (CurrFilteredpoint); if (m_nFiltering >= 5) WorkReading = CharFilter(CurrFil- teredPoint); // Store the reading in the CardioGram // m_dwStopDrawing should always point to the most recently inserted Car doGram Point. if( mbDrawECG ) { m_dwStopDrawing++; if (m_dwStopDrawing >= m_dwCardioGramLength) m_dwStopDrawing = 0; m_pSardioGram[m_dwStopDrawing] = WorkReading; // Flag the axis, if necessary if (SampleCount == 0) m_pVertical Bar [m_dwStopDrawing] = 1; else m_pVertical Bar [m_dwStopDrawing] = 0; SampleCount++; if (SampleCount >= ECG_FREQ) SampleCount = 0; } // end if mbDrawECG // Detect QRS complexes // If this seems overly simplified, set Filtering property to 5 and // take a look at the fully filtered data. // First, find max value in last 2 seconds of data. m_nBPM = mBPM.Update( CurrFilteredPoint ); m_nAvgBPM = mBPM.AvgBPM(); } // end for thru data if( !mbDrawECG ) { mCumSamps += NumData; if( mCumSamps >= mSampsPerBPMPt ) { int   minHr =min( nLnAvgBPM, mMaxHR ); mBPMData.push_back( minHr ); mCumSamps = 0; // mPointsRead tells us where to put // the point on the screen when t e // number of screen points is less // than the number of data points ++mPointsRead; if( mPointsRead >= mPointsPerCyc ) mPointsRead = 0; FireViewChange(); } // end if time to draw again } // end if ! drawEcG else FireViewChange(); } // end of NewRawData void CWebEcg: ConnectToServer() { USES_CONVERSION; m_bConnectedToServer = true; m_sckLocalSocket = socket(PF_INET, SOCK_STREAM, 0); m_sckadrRemoteSocketAddress.sin_family = PF_INET; m_sckadrRemoteSocketAddress.sinport = htons(4321); m_sckadrRemoteSocketAddress.sinaddr.s_addr = GetAddr(W2A(m_bstrServerAddrE s)); m_sckadrRemoteSocketAddress.sin_zero[0] = 0; m_sckadrRemoteSocketAddress.sin_zero[1] = 0; m_sckadrRemoteSocketAddress.sin_zero[2] = 0; m_sckadrRemoteSocketAddress.sin_zero[3] = 0; m_sckadrRemoteSocketAddress.sin_zero[4] = 0; m_sckadrRemoteSocketAddress.sin_zero[5] = 0; m_sckadrRemoteSocketAddress.sin_zero[6] = 0; m_sckadrRemoteSocketAddress.sin_zero[7] = 0; if (connect (m_sckLocalSocket, (sockaddr *) (&m_sckadrRemoteSocketAddress), sizeof(m_sckadrRemoteSocketAddress))) { // Some kind of problem... // 05/02 - No window here.... char buff[200]; sprintf( buff, “Connect: Could not attach to host: %s”, m_bstrServerAddress.m_str ); MessageBox( buff, “DR DAVE”, MB_OK); m_bConnectedToServer = false; } } STDMETHODIMP CWebEcg::get_AddNewPoint(short * pVal) { // TODO: Add your implementation code here *pVal = 0; return S_OK; } STDMETHODIMP CWebEcg::put.AddNewPoint(short newVal) { // TODO: Add your implementation code here if (FireonRequestEdit (dispidDetectPercent) == S_FALSE) { return S_FALSE; } NewRawData(&newVal, 1); SetDirty(TRUE); FireOnChanged(dispidDetectPercent); return S_OK; } STDMETHODIMP CWebEcg::get_Age( short *pVal ) { *pVal = mAge; return S_OK; } STDMETHODIMP CWebEcg::put_Age( short newVal ) { mAge = newVal; return S_OK; } STDMETHODIMP CWebEcg::get_Average_Count(long *pVal) { *pVal = mBPM.AverageCount(); return S_OK; } STDMETHODIMP CWebEcg::put_Average_Count(long newVal) { mBPM.AverageCount( newVal ); return S_OK; } STDMETHODIMP CWebEcg::get_ViewECG(BOOL *pVal) { if( mbDrawECG ) *pVal = TRUE; else *pVal = FALSE; return S_OK; } STDMETHODIMP CWebEcg::put_ViewECG (BOOL newVal) { if( newVal == TRUE ) mbDrawECG = true; else mbDrawECG = false; return S_OK; } inline void DrawLine(HDC hdc, long x1, long y1, long x2, long y2) { MoveToEx(hdc, x1, y1, NULL); LineTo (hdc, x2, y2); } void DrawString( HDC =tl,15 dc, HFONT font, UINT align, COLORREF col, int left, int top char *str ) { SelectObject( dc, font ); SetTextAlign( dc, align ); SetTextColor( dc, col ); Textout( dc, left, top, _T(str), strlen(str) ); } // end of Drawstring #include <olectl.h> // WebEcgControl.idl : IDL source for WebEcgControl.dll // // This file will be processed by the MIDL tool to // produce the type library (WebEcgControl.tlb) and marshalling code. import “oaidl.idl”; import “ocidl.idl”; typedef enum propertydispids tl,3 { dispidGridColor = 2, dispidGridStyle = 3, dispidMaxmV = 4, dispidMinmV = 5, dispidStepmV = 6, dispidForewidth = 7, dispidDisplaySeconds = 8, dispidvolumeThreshold = 9, dispidFiltering = 10, dispidBPMFontSize = 11, dispidDetectPercent = 12, dispidServerAddress = 13, dispidMicrophoneNotServer = 14, dispidNewPointByte1 = 15, dispidNewPointByte2 = 16, dispidAddNewPoint = 17, }PROPERTYDISPIDS; [ object uuid(E254A2C1-B470-11D2-8455-00104B05249C), dual, helpstring(“IwebEcg Interface”), pointer_default(unique) ] interface IwebEcg : IDispatch { [propput, id(DISPID_BACKCOLOR)] HRESULT BackColor([in]OLE_COLOR clr); [propget, id(DISPID_BACKCOLOR)] HRESULT BackColor([out, retval]OLE_COLOR* pclr); [propput, id(DISPID_BORDERCOLOR)] HRESULT BorderColor([in]OLE_COLOR clr); [propget, id(DISPID_BORDERCOLOR)] HRESULT BorderColor([out, retval]OLE_COLOR* pclr); [propput, id(DISPID_BORDERSTYLE)] HRESULT BorderStyle([in]long style); [propget, id(DISPID_BORDERSTYLE)] HRESULT BorderStyle([out, retval]long* pstyle); [propput, id(DISPID_BORDERWIDTH)] HRESULT BorderWidth([in]long width); [propget, id(DISPID_BORDERWIDTH)] HRESULT BorderWidth([out, retval]long* width); [propput, id(DISPID_FORECOLOR)] HRESULT ForeColor([in]OLE_COLOR clr); [propget, id(DISPID_FORECOLOR)] HRESULT ForeColor([out, retval]OLE_COLOR* pclr); [propput, id(DISPID_BORDERVISIBLE)] HRESULT BorderVisible([in]VARIANT_BOOL vbool); [propget, id(DISPID_BORDERVISIBLE)] HRESULT Bordervisible([out, retval]VARIANT_BOOL* pbool); [propput, id(DISPID_MOUSEPOINTER)] HRESULT MousePointer[in]long pointer); [propget, id(DISPID_MOUSEPOINTER)] HRESULT MousePointer([out, retval]long* ppointer); [propput, id (DISPID_MOUSEICON)] HRESULT MouseIcon([in]IpictureDisp* pMouseIcon); [propput, id(DISPID_MOUSEICON)] HRESULT MouseIcon([in]IpictureDisp* pMouseIcon); [propget, id (DISPID_MouseIcon)] HRESULT MouseIcon([out, retval]IPictureDisp** ppMouseIcon); [propget, id(dispidGrldColor), helpstring( “property GridColor”)] HRESULT GridColor([out, retval]OLE_COLOR *pVal); [propput, id(dispidGridColor), helpstring(“property GridColor”)] HRESULT GridColor[in] OLE_COLOR newVal); [propget, id(dispidGridStyle), helpstring(“property GridStyle”)] HRESULT GridStyle([out, retval] long *pVal); [propput, id(dispidGridStyle), helpstring(“property GridStyle”)] HRESULT GridStyle([in] long newVal); [propget, id(dispidMinmV), helpstring(“property MinmV”)] HRESULT MinmV([o ut, retval] double *pVal); [propput, id(dispidMinmV), helpstring(“property MinmV”)] HRESULT MinmV([i n] double newVal); [propget, id(dispidMaxmV), helpstring(“property MaxmV”)] HRESULT MaxmV([o ut, retval] double *pVal); [propput, id(dispidMaxmV), helpstring(“property MaxmV”)] HRESULT MaxmV([i n] double newVal); [propget, id(dispidStepmV), helpstring(“property StepmV”)] HRESULT StepmV ([out, retval] double *pVal); [propput, id(dispidStepmV), helpstring(“property StepmV”)] HRESULT StepmV ([in] double newVal); [propget, id (dispidForeWidth), helpstring(“property ForeWidth”)] HRESULT ForeWidth([out, retval] long *pVal); [propput, id(dispidForeWidth), helpstring(“property ForeWidth”)] HRESULT ForeWidth([in] long newVal); [propget, id (dispidDisplaySeconds), helpstring (“property DisplaySeconds”) ] HRESULT DisplaySeconds([out, retVal] short *pVal); [propput, id(dispidDisplaySeconds), helpstring(“property DisplaySeconds”) ] HRESULT DisplaySeconds([in] short newVal), [propget, id(dispidVolumeThreshold), helpstring(“property VolumeThreshold ”)] HRESULT VolumeThreshold([out, retVal] short *pVal); [propput, id(dispidVolumeThreshold), helpstring(“property VolumeThreshold ”)] HRESULT VolumeThreshold([in] short newVal); [propget, id(dispidFiltering), helpstring(“property Filtering”)] HRESULT Filtering ([out, retval] short *pVal). [propput, id(dispidFiltering), helpstring(“property Filtering”)] HRESULT Filtering ([in] short newVal); [propget, id(dispidBPMFontSize), helpstring (“property BPMFontSize”)] HRES ULT BPMFontSize([out, retval] short *pVal); [propput, id(dispidBPMFontSize), helpstring (“property BPMFontSize”)] HRES ULT BpMFontSize([in] short newVal); [propget, id(dispidDetectPercent), helpstring(“property DetectPercent”)] HRESULT DetectPercent([out, retval] double *pVal); [propput, id(dispidDetectPercent), helpstring(”property DetectPercent”)] HRESULT DetectPercent([in] double newVal); [propget, id(dispidServerAddress), helpstring(“property ServerAddress”)] HRESULT ServerAddress([out, retval] BSTR *pVal); [propput, id(dispidServerAddress), helpstring(“property ServerAddress”)] HRESULT ServerAddress([in] BSTR newVal); [propget, id(dispidMicrophoneNotServer), helpstring (“property MicrophoneN otServer”)] HRESULT MicrophoneNotServer([out, retval] short *pVal); [propput, id(dispidMicrophoneNotServer), helpstring (“property MicrophoneN otserver”)] HRESULT MicrophoneNotServer([in] short newVal); [propget, id(dispidAddNewPoint), helpstring (“property AddNewPoint”)] HRES ULT AddNewPoint([out, retval] short *pVal); [propput, id(dispidAddNewpoint), helpstring(“property AddNewPoint”)] HRES ULT AddNewPoint([in] short newVal); }; [ uuid(E254A2B4-B470-11D2-8455-00104B05249C), version (1.0) helpstring(“WebEcgControl 1.0 Type Libraryt”) ] library WEBECGCONTROLLib { importlib(“stdole32.tlb”); importlib(“stdole2.tlb”); [ uuid(E254A2C2-B470-11D2-8455-00104B05249C), helpstring(“WebEcg Class”) ] coclass WebEcg { [default] interface IWebEcg; }; [ uuid(3D002C02-B9E0-11D2-8455-00104B05249C), helpstring(“WebEcgPPG Class”) ] coclass WebECgPPG { interface Iunknown; }; }; // WebEcgControl.cpp : Implementation of DLL Exports. // Note: Proxy/Stub Information // To build a separate proxy/stub DLL, // run nmake −f webEcgControlps.mk in the project directory. #include “stdafx.h” #include ”resource.h” #include “initguid.h” #include “WebEcgControl.h” #include “WebEcgControl_.i.c” #include “WebEcg.” #include “WebEcgPPG.h” CComModule _Module; BEGIN_OBJECT_MAP(ObjectMap) OBJECT_ENTRY(CLSID_WebEcg, CwebEcg) OBJECT_ENTRY(CLSID_WebEcgPPG, CWebEcgPPG) END_OBJECT_MAP() ////////////////////////////////////////////////////////////////////////////////////////////////////////// // DLL Entry Point extern “C” BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/) { if (dwReason == DLL_PROCESS_ATTACH) { _Module.Init(ObjectMap, hInstance); DisableThreadLibraryCalls(hInstance); } else if (dwReason == DLL_PROCESS_DETACH) _Module.Term(); return TRUE;   // ok } ////////////////////////////////////////////////////////////////////////////////////////////////////////// // Used to determine whether the DLL can be unloaded by OLE STSTDAPI DllCanUnloadNow(void) { return (_Module.GetLockCount()==0) ? S_OK : S_FALSE; } ////////////////////////////////////////////////////////////////////////////////////////////////////////// // Returns a class factory to create an object of the requested type STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) { return (_Module.GetClassObject(rclsid, riid, ppv); } ////////////////////////////////////////////////////////////////////////////////////////////////////////// // DllRegisterServer - Adds entries to the system registry STDAPI DllRegisterServer(void) { // registers object, typelib and all interfaces in typelib return _Module.RegisterServer(TRUE); ////////////////////////////////////////////////////////////////////////////////////////////////////////// // DllUnregisterServer - Removes entries from the system registry STDAPI DllUnregisterServer(void) { _Module.UnregisterServer(); return S_OK; } // resource.h //{{NO_DEPENDENCIES}} // Microsoft Developer Studio generated include file. // Used by WebEcgControl.rc // #define IDS_PROJNAME 100 #define IDR_WEBECG 101 #define IDS_TITLEWebEcgPPG 102 #define IDS_HELPFILEWebEcgPPG 103 #define IDS_DOCSTRINGWebECgPPG 104 #define IDR_WEBECGPPG 105 #define IDD_WEBECGPPG 106 #define IDC_FORE_WIDTH 201 #define IDC_FILENAME 202 #define IDC_EDIT2 203 #define IDC_GRID_STYLE 204 #define IDC_MAX_MV 205 #define IDC_MIN_MV 206 #define IDC_STEP_MV 207 #define IDC_DISPLAY_SECONDS 208 #define IDC_VOLUME_THRESHOLD 209 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 201 #define _APS_NEXT_COMMAND_VALUE 32768 #define _APS_NEXT_CONTROL_VALUE 210 #define _APS_NET_SYMED_VALUE 107 #endif #endif // stdafx.h include file for standard system include files, // or project specific include files that are used frequently, // but are changed infrequently #if !defined (AFC_STDAFX_H_E254A2B8_B- 470_11D2_8455_00104B05249C__INCLUDED_) #define AFX_STDAFX_H_E254A2B8_B470_11- D2_8455_00104B05249C_INCLUDED_ #if _MSC_VER >= 1000 #pragma once #endif // _MSC_VER >= 1000 #define STRICT #define _WIN32_WINNT 0x0400 #define _ATL_APARTMENT_THREADED #include <atlbase.h> //You may derive a class from CComModule and use it if you want to override //something, but do not change the name of _Module extern CComModule _Module; #include <atlcom.h> #include <atlctl.h> #include <ocidl.h>// Added by Classview //{{AFC_INSERT_LOCATION}} // Microsoft Developer Studio will insert additional declarations immediately bef ore the previous line. #endif // !defined (AFX_STDAFX_H_E254A2B8_B- 470_11D2_8455_00104B05249C_INCLUDED) // stdafx.cpp : source file that includes just the standard includes //  stdafx.pch will be the pre-compiled header //  stdafx.obj will contain the pre-compiled type information #include “stdafx.h” #ifdef _ATL_STATIC_REGISTRY #include <statreg.h> #include <statreg.cpp> #endif #include <atlimpl.cpp> #include <atlctl.cpp> #include <atlwin.cpp> 

What is claimed is:
 1. A system for generating and transferring medical data, comprising: means for sensing a biological function or condition; and means, communicating with the means for sensing, for transferring a response to the sensed biological function or condition over the Internet.
 2. A system as defined in claim 1 , wherein: the means for sensing provides an audible output signal in response to the sensed biological function or condition; and the means for transferring includes: a microphone to respond to the audible output signal; an electrical circuit connected to the microphone to convert an analog signal from the microphone to a digital signal; and a microprocessor circuit connected to the electrical circuit and programmed to access the Internet and to transfer a representation of the audible output signal over the Internet to a selected Internet site in response to the digital signal.
 3. A system as defined in claim 1 , further comprising means for receiving the transferred response over the Internet.
 4. A system as defined in claim 3 , wherein the means for receiving includes a computer located at a medical facility and connected to communicate with the Internet, wherein the computer is programmed to receive the response transferred over the Internet and to provide a visible representation of the sensed biological function or condition.
 5. A system as defined in claim 3 , wherein the means for receiving includes a computer located at a data communication service provider facility and connected to communicate with the Internet, wherein the computer is programmed to receive the response transferred over the Internet, store the response in a database, and transmit in response thereto another signal to an end user.
 6. A system for generating and transferring medical data, comprising: a sensor used by a human user to sense a function or condition of the user's body; and a personal computer located with the sensor and having a microphone connector and analog to digital conversion means communicated with the sensor such that a digital processing circuit of the personal computer receives from the analog to digital conversion means a digital electric signal derived from an analog electric signal received through the microphone connector in response to the user's body function or condition sensed by the sensor, wherein the personal computer is programmed to transfer data representative of the received digital electric signal over a computer communication network contemporaneously with the sensor sensing the user's body function or condition.
 7. A system as defined in claim 6 , further comprising a recipient computer located at a medical facility and connected to communicate with the computer communication network, wherein the recipient computer is programmed to receive the data transferred over the computer communication network and to provide a visible representation of the sensed body function or condition and to store the data in a searchable database.
 8. A system as defined in claim 7 , wherein: the sensor generates an acoustical signal; and the system further comprises a microphone connected to the microphone connector to provide the analog electric signal in response to the acoustical signal.
 9. A system as defined in claim 6 , further comprising a recipient computer located at a data communication service provider facility and connected to communicate with the computer communication network, wherein the recipient computer is programmed to receive the data transferred over the computer communication network and to transmit in response thereto another signal to an end user.
 10. A system as defined in claim 9 , wherein: the sensor generates an acoustical signal; and the system further comprises a microphone connected to the microphone connector to provide the analog electric signal in response to the acoustical signal.
 11. A system as defined in claim 6 , wherein: the sensor generates an acoustical signal; and the system further comprises a microphone connected to the microphone connector to provide the analog electric signal in response to the acoustical signal.
 12. A system for generating and transferring medical data in real time, comprising: a sensor to provide an acoustical signal in simultaneous response to a biological function or condition; a microphone, located with the sensor, to receive the acoustical signal as the acoustical signal is provided from the sensor; and a computer connected to the microphone and adapted to transfer over the Internet, in real-time response to the microphone receiving the acoustical signal, electric signals representative of the received acoustical signal.
 13. A system as defined in claim 12 , wherein the sensor includes a hand-held transducer that converts energy from an adjacent beating heart of a patient into the acoustical signal.
 14. A system as defined in claim 13 , wherein the computer is a personal computer including: a microprocessor circuit that has a microprocessor which operates at a nominal speed of at least twenty megahertz, and a microphone interface circuit connected to the microphone and the microprocessor circuit.
 15. A system as defined in claim 14 , wherein the personal computer is programmed with a web browser and a medical data acquisition and transmission program.
 16. A system as defined in claim 15 , wherein the medical data acquisition and transmission program is downloaded into the personal computer from an Internet site accessed using the web browser.
 17. A system as defined in claim 12 , wherein the computer is programmed with a web browser and a medical data acquisition and transmission program.
 18. A system as defined in claim 17 , wherein the medical data acquisition and transmission program is from an Internet site accessed using the web browser.
 19. A system for generating and transferring data, comprising: a plurality of initial user sites, each of the initial user sites having a data generating source and a computer connected to receive signals from the data generating source and to access the Internet; and an Internet site addressable from each of the plurality of initial user sites such that each initial user site can access a data acquisition and transmission program through the Internet site to enable the respective computer to process received signals from the respective data generating source and transmit responsive signals over the Internet.
 20. A system as defined in claim 19 , further comprising a recipient site different from the initial user sites and the Internet site, the recipient site connected to receive over the Internet at least a portion of responsive signals transmitted over the Internet.
 21. A system as defined in claim 20 , wherein the recipient site provides the data acquisition and transmission program to the Internet site.
 22. A system as defined in claim 19 , further comprising a recipient computer located at a medical facility and connected to communicate with the Internet, wherein the recipient computer is programmed to receive at least part of the responsive signals transmitted over the Internet.
 23. A system as defined in claim 19 , further comprising a recipient computer located at a data communication service provider facility and connected to communicate with the Internet, wherein the recipient computer is programmed to receive at least part of the responsive signals transmitted over the Internet and to transmit in response thereto end user signals from the recipient computer to an end user, wherein the end user signals are a representation of data from at least one said data generating source.
 24. A method of generating and transferring data, comprising: accessing an Internet site from a computer where a data generating source is located; downloading to the computer from the accessed Internet site a data acquisition and transmission program; and operating the computer with the downloaded data acquisition and transmission program such that the computer receives signals from the data generating source, processes the received signals, and transmits data signals onto the Internet in response thereto.
 25. A method as defined in claim 24 , further comprising receiving, over the Internet, the transmitted data signals at a medical facility.
 26. A method as defined in claim 24 , further comprising receiving, over the Internet, the transmitted data signals at a data communication service provider facility.
 27. A method as defined in claim 26 , further comprising transmitting, from the data communication service provider facility to an end user, end user signals responsive to the received data signals.
 28. A method as defined in claim 27 , wherein the end user signals are transmitted through a paging network to a physician.
 29. A method of generating and transferring medical data, comprising: transducing directly from a patient a human body function or condition into a first signal; converting at the site of the patient the first signal into a second signal adapted for transmission over the Internet; and transmitting the second signal over the Internet to a recipient.
 30. A method as defined in claim 29 , wherein transducing includes the patient manipulating a portable sensor adjacent the patient's body such that the sensor senses the human body function or condition and generates the first signal in response thereto.
 31. A method as defined in claim 30 , wherein the sensor generates an acoustical signal as the first signal.
 32. A method as defined in claim 31 , wherein converting the first signal into a second signal includes receiving the acoustical signal at a microphone having an output connected to a computer adapted for processing an output signal of the microphone to provide the second signal in response thereto.
 33. A method as defined in claim 32 , wherein converting the first signal into the second signal further includes downloading into the computer a medical data acquisition and transmission program from the recipient.
 34. A method as defined in claim 32 , wherein converting the first signal into the second signal further includes downloading into the computer, from an Internet site different from the recipient, a medical data acquisition and transmission program.
 35. A method as defined in claim 29 , wherein converting the first signal into the second signal includes downloading into a computer a medical data acquisition and transmission program from the recipient.
 36. A method as defined in claim 29 , wherein converting the first signal into the second signal includes downloading into a computer, from an Internet site different from the recipient, a medical data acquisition and transmission program.
 37. A method as defined in claim 29 , further comprising performing the transducing, converting, and transmitting contemporaneously, and contemporaneously therewith generating at the recipient a display in response to the transmitted second signal, wherein the display is a real-time representation of the transduced human body function or condition.
 38. A method as defined in claim 29 , further comprising transmitting a medical data signal from the recipient to a second recipient in response to the first-mentioned recipient receiving the transmitted second signal.
 39. A method as defined in claim 38 , further comprising performing the transducing, converting and two transmitting steps contemporaneously, and contemporaneously therewith generating at the second recipient a display in response to the transmitted medical data signal, wherein the display is a real-time representation of the transduced human body function or condition.
 40. A method as defined in claim 38 , wherein the medical data signal is transmitted through a paging network.
 41. A method of generating and transferring medical data, comprising: generating an acoustical signal in response to a human body function or condition; converting the acoustical signal into a corresponding electric signal; processing the electric signal in the computer to provide a data signal; and transmitting the data signal onto the Internet from the computer.
 42. A method as defined in claim 41 , wherein the human body function or condition is selected from the group consisting of heartbeat activity, brain waves, organ or limb size, blood flow, and galvanic skin resistance.
 43. A method as defined in claim 42 , wherein converting the acoustical signal includes receiving the acoustical signal in a microphone located near the patient and connected to the computer.
 44. A method as defined in claim 41 , wherein generating an acoustical signal includes a patient holding a portable heart monitor against the patient's chest where the patient's heartbeat energy is detected such that the portable heart monitor generates the acoustical signal in response to the heartbeat energy.
 45. A method as defined in claim 44 , wherein converting the acoustical signal includes receiving the acoustical signal in a microphone located near the patient and connected to the computer.
 46. A method of generating and transferring medical data, comprising steps of: (a) accessing a site on a computer communication network with a personal computer located with a patient; (b) downloading from the accessed site to the personal computer a medical data acquisition and transmission program; (c) holding a heart monitor against a patient and converting with the heart monitor energy from the beating heart of the patient into acoustical signals; (d) receiving the acoustical signals in a microphone near the heart monitor and converting with the microphone the received acoustical signals into analog electric signals; (e) communicating the analog electric signals from the microphone to an analog to digital conversion circuit of the personal computer and converting thereby the analog electric signals to digital electric signals; and (f) processing the digital electric signals in the personal computer under control of the medical data acquisition and transmission program; wherein at least steps (c) through (f) are performed together in real time.
 47. A method as defined in claim 46 , further comprising accessing from the personal computer a second site on the computer communication network, and transmitting patient heart data over the computer communication network to the accessed second site in real-time response to the processed digital electric signals, both under control of the medical data acquisition and transmission program in the personal computer.
 48. A method as defined in claim 47 , further comprising contemporaneously transmitting, from a computer at the second site, the patient heart data over a wireless communication network to a physician.
 49. A method of monitoring biological functions and conditions of a plurality of patients, comprising: distributing to each patient at least one sensor to detect at least one biological function or condition of the patient; and maintaining a medical data acquisition and transmission program at an Internet site accessible by the patients such that the patients can use, from computers at the locations of the patients, the medical data acquisition and transmission program to control their respective computers to receive and process signals from the patients' respective sensors and to transmit medical data onto the Internet in response thereto.
 50. A method as defined in claim 49 , further comprising distributing to a plurality of physicians receivers for receiving at least portions of the medical data transmitted over the Internet.
 51. A method as defined in claim 50 , further comprising providing a combination Internet receiving and wireless network data transmitting site to receive the medical data transmitted on the Internet and to transmit received medical data to at least one respective physician's receiver through a wireless network.
 52. A method as defined in claim 49 , further comprising marking each sensor with indicia defining the address of the Internet site.
 53. A method as defined in claim 49 , wherein maintaining the medical data acquisition and transmission program includes storing the program in a computer at the Internet site and wherein the method further comprises storing a database of potential recipients of the medical data, the database also accessible by each patient such that each patient can identify from the potential recipients at least one selected recipient to receive the medical data for that patient.
 54. A method as defined in claim 53 , further comprising communicating, to a respective patient's at least one selected recipient, the medical data transmitted onto the Internet for that patient.
 55. A method as defined in claim 49 , further comprising using the Internet to receive, at a data communication service provider Internet site, the medical data transmitted onto the Internet from the plurality of patients.
 56. A method as defined in claim 55 , further comprising transmitting a respective patient's medical data, received at the data communication service provider Internet site, from the data communication service provider Internet site to a respective medical end user.
 57. A method as defined in claim 56 , wherein transmitting to a respective medical end user includes transmitting the respective patient's medical data over a wireless network.
 58. A remote heart monitoring method, comprising: distributing to each of a plurality of patients a respective portable heart monitoring device that the respective patient can hold against his or her chest without assistance; maintaining a medical data acquisition and transmission program at a first Internet site known to the patients such that each patient can access, using a web browser program of a computer at the location of the patient, the medical data acquisition and transmission program to control the computer to receive and process signals from the patient's respective heart monitoring device and to transmit medical data onto the Internet in response thereto and in response to automatic operation of the medical data acquisition and transmission program; receiving the transmitted medical data at a second Internet site; and communicating, from a computer at the second Internet site, respective portions of the medical data to respective end users of the medical data.
 59. A method as defined in claim 58 , wherein communicating respective portions of the medical data includes transmitting the respective portions over a paging network to hand-held computers of respective end user medical providers.
 60. A method as defined in claim 59 , further comprising marking each heart monitoring device with the web address of the first Internet site.
 61. A method as defined in claim 58 , further comprising marking each heart monitoring device with the web address of the first Internet site.
 62. A method of generating medical data representative of a biological function or condition of an individual, comprising: (a) accessing an Internet site with a personal computer located with the individual, wherein the personal computer is programmed with a conventional operating program; (b) downloading from the accessed Internet site to the personal computer a medical data acquisition program automatically operable with the conventional operating program; (c) generating an acoustical signal in response to the biological function or condition of the individual; (d) receiving the acoustical signal in a microphone near the individual and converting with the microphone the received acoustical signal into an analog electric signal; (e) communicating the analog electric signal from the microphone to an analog to digital conversion circuit of the personal computer and converting thereby the analog electric signal to a digital electric signal; (f) processing the digital electric signal in the personal computer under control of the medical data acquisition program; and (g) displaying to the individual, through the personal computer, a representation of the individual's biological function or condition in response to which the acoustical signal was generated.
 63. A method as defined in claim 62 , wherein at least steps (c) through (g) are performed together in real time.
 64. A method as defined in claim 63 , further comprising in real time with at least steps (c) through (g), transmitting from the personal computer over the Internet data responsive to the processed digital electric signal.
 65. A method as defined in claim 62 , further comprising in real time with at least steps (c) through (g), transmitting from the personal computer over the Internet data responsive to the processed digital electric signal. 